From 70ae733883fb14ad325aec9ad7f79e133896ce89 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Fri, 27 May 2022 12:22:05 -0400 Subject: [PATCH] Improvements to MoveValidator API. (#10517) - Reduces number of required params for move validation. - Moves combat move bool to ctor. - Moves special move validation from delegates to MoveValidator. - Updates call sites. - Some misc clean ups. --- .../strategy/engine/data/RouteFinder.java | 5 +- .../triplea/ai/pro/ProNonCombatMoveAi.java | 4 +- .../strategy/triplea/ai/pro/ProTechAi.java | 15 +- .../ai/pro/data/ProTerritoryManager.java | 4 +- .../triplea/ai/pro/util/ProMatches.java | 2 +- .../triplea/ai/pro/util/ProMoveUtils.java | 8 +- .../strategy/triplea/ai/weak/WeakAi.java | 65 +++--- .../delegate/AbstractMoveDelegate.java | 20 +- .../strategy/triplea/delegate/Matches.java | 5 - .../triplea/delegate/MoveDelegate.java | 5 +- .../triplea/delegate/SpecialMoveDelegate.java | 193 +---------------- .../delegate/battle/MustFightBattle.java | 2 +- .../steps/retreat/DefensiveSubsRetreat.java | 2 +- .../move/validation/MoveValidator.java | 198 ++++++++++++++++-- .../ui/panel/move/MovableUnitsFilter.java | 9 +- .../triplea/delegate/WW2V3Year41Test.java | 10 +- .../move/validation/MoveValidatorTest.java | 74 +++---- 17 files changed, 289 insertions(+), 332 deletions(-) diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/RouteFinder.java b/game-app/game-core/src/main/java/games/strategy/engine/data/RouteFinder.java index 120907a41e5..77a31e852ff 100644 --- a/game-app/game-core/src/main/java/games/strategy/engine/data/RouteFinder.java +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/RouteFinder.java @@ -31,7 +31,7 @@ class RouteFinder { @Nullable private final GamePlayer player; RouteFinder(final GameMap map, final Predicate condition) { - this(new MoveValidator(map.getData()), map, condition, Set.of(), null); + this(map, condition, Set.of(), null); } RouteFinder( @@ -39,7 +39,8 @@ class RouteFinder { final Predicate condition, final Collection units, final GamePlayer player) { - this(new MoveValidator(map.getData()), map, condition, units, player); + // Note: We only use MoveValidator for canal checks, where isNonCombat isn't used. + this(new MoveValidator(map.getData(), false), map, condition, units, player); } Optional findRouteByDistance(final Territory start, final Territory end) { diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java index c9557057e08..56c06167ef0 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java @@ -2483,8 +2483,8 @@ private Map moveInfraUnits( u, player); final MoveValidationResult mvr = - new MoveValidator(data) - .validateMove(new MoveDescription(List.of(u), r), player, true, null); + new MoveValidator(data, true) + .validateMove(new MoveDescription(List.of(u), r), player); if (!mvr.isMoveValid()) { continue; } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProTechAi.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProTechAi.java index efb98922aac..d3502baa547 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProTechAi.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/ProTechAi.java @@ -421,6 +421,7 @@ private static List findAttackers( Territory current; distance.put(start, 0); visited.put(start, null); + MoveValidator moveValidator = new MoveValidator(data, false); while (!q.isEmpty()) { current = q.remove(); if (distance.getInt(current) == maxDistance) { @@ -431,11 +432,9 @@ private static List findAttackers( if (!neighbor.anyUnitsMatch(unitCondition) && !routeCondition.test(neighbor)) { continue; } - if (sea) { - final Route r = new Route(neighbor, current); - if (new MoveValidator(data).validateCanal(r, null, player) != null) { - continue; - } + if (sea + && moveValidator.validateCanal(new Route(neighbor, current), null, player) != null) { + continue; } distance.put(neighbor, distance.getInt(current) + 1); visited.put(neighbor, current); @@ -444,11 +443,7 @@ private static List findAttackers( if (ignoreDistance.contains(dist)) { continue; } - for (final Unit u : neighbor.getUnitCollection()) { - if (unitCondition.test(u)) { - units.add(u); - } - } + units.addAll(neighbor.getUnitCollection().getMatches(unitCondition)); } } } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java index 61d1b14ec09..a21e96465cd 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java @@ -1117,6 +1117,7 @@ private static void findAmphibMoveOptions( int movesLeft = getUnitRange(data, myTransport, myUnitTerritory, player, isCheckingEnemyAttacks) .intValue(); + MoveValidator moveValidator = new MoveValidator(data, !isCombatMove); while (movesLeft >= 0) { final Set nextTerritories = new HashSet<>(); for (final Territory currentTerritory : currentTerritories) { @@ -1126,8 +1127,7 @@ private static void findAmphibMoveOptions( gameMap.getNeighbors(currentTerritory, canMoveSeaUnitsThrough); for (final Territory possibleNeighborTerritory : possibleNeighborTerritories) { final Route route = new Route(currentTerritory, possibleNeighborTerritory); - if (new MoveValidator(data).validateCanal(route, List.of(myTransport), player) - == null) { + if (moveValidator.validateCanal(route, List.of(myTransport), player) == null) { nextTerritories.add(possibleNeighborTerritory); } } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java index 9c55c722eb4..da636e9bbbd 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java @@ -31,7 +31,7 @@ public static BiPredicate noCanalsBetweenTerritories( final GamePlayer player, final GameData gameData) { return (startTerritory, endTerritory) -> { final Route r = new Route(startTerritory, endTerritory); - return new MoveValidator(gameData).validateCanal(r, null, player) == null; + return new MoveValidator(gameData, false).validateCanal(r, null, player) == null; }; } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java index 0a5d0c3abba..b8ca9d0a6c5 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java @@ -235,12 +235,12 @@ public static List calculateAmphibRoutes( ProMatches.territoryCanMoveSeaUnitsThrough(data, player, isCombatMove)); Territory territoryToMoveTo = null; int minUnitDistance = Integer.MAX_VALUE; - int maxDistanceFromEnd = - Integer.MIN_VALUE; // Used to move to farthest away loading territory first + // Used to move to farthest away loading territory first + int maxDistanceFromEnd = Integer.MIN_VALUE; + MoveValidator moveValidator = new MoveValidator(data, !isCombatMove); for (final Territory neighbor : neighbors) { final Route route = new Route(transportTerritory, neighbor); - if (new MoveValidator(data).validateCanal(route, List.of(transport), player) - != null) { + if (moveValidator.validateCanal(route, List.of(transport), player) != null) { continue; } int distanceFromUnloadTerritory = 0; diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ai/weak/WeakAi.java b/game-app/game-core/src/main/java/games/strategy/triplea/ai/weak/WeakAi.java index 044658af0a3..53859c9fdfc 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ai/weak/WeakAi.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ai/weak/WeakAi.java @@ -26,6 +26,7 @@ import games.strategy.triplea.delegate.TransportTracker; import games.strategy.triplea.delegate.battle.BattleDelegate; import games.strategy.triplea.delegate.data.PlaceableUnits; +import games.strategy.triplea.delegate.move.validation.MoveValidator; import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate; import games.strategy.triplea.delegate.remote.IMoveDelegate; import games.strategy.triplea.delegate.remote.IPurchaseDelegate; @@ -319,7 +320,7 @@ private static List calculateNonCombatSea( final Predicate routeCond = Matches.territoryIsWater() .and(Matches.territoryHasEnemyUnits(player, data.getRelationshipTracker()).negate()) - .and(Matches.territoryHasNonAllowedCanal(player, data).negate()); + .and(territoryHasNonAllowedCanal(player, data).negate()); Route r = data.getMap().getRoute(start, destination, routeCond); if (r == null || r.hasNoSteps() || !routeCond.test(destination)) { return null; @@ -330,6 +331,12 @@ private static List calculateNonCombatSea( return r; } + private static Predicate territoryHasNonAllowedCanal( + final GamePlayer player, final GameData gameData) { + return t -> + new MoveValidator(gameData, false).validateCanal(new Route(t), null, player) != null; + } + private static List calculateCombatMoveSea( final GameState data, final GamePlayer player) { final var moves = new ArrayList(); @@ -385,21 +392,21 @@ private static Route getAlternativeAmphibRoute(final GamePlayer player, final Ga // should select all territories with loaded transports final Predicate transportOnSea = Matches.territoryIsWater().and(Matches.territoryHasLandUnitsOwnedBy(player)); + final Predicate ownedTransports = + Matches.unitCanTransport() + .and(Matches.unitIsOwnedBy(player)) + .and(Matches.unitHasNotMoved()); + final Predicate enemyTerritory = + Matches.isTerritoryEnemy(player, data.getRelationshipTracker()) + .and(Matches.territoryIsLand()) + .and(Matches.territoryIsNeutralButNotWater().negate()) + .and(Matches.territoryIsEmpty()); Route altRoute = null; final int length = Integer.MAX_VALUE; for (final Territory t : data.getMap()) { if (!transportOnSea.test(t)) { continue; } - final Predicate ownedTransports = - Matches.unitCanTransport() - .and(Matches.unitIsOwnedBy(player)) - .and(Matches.unitHasNotMoved()); - final Predicate enemyTerritory = - Matches.isTerritoryEnemy(player, data.getRelationshipTracker()) - .and(Matches.territoryIsLand()) - .and(Matches.territoryIsNeutralButNotWater().negate()) - .and(Matches.territoryIsEmpty()); final int trans = t.getUnitCollection().countMatches(ownedTransports); if (trans > 0) { final Route newRoute = Utils.findNearest(t, enemyTerritory, routeCondition, data); @@ -414,6 +421,19 @@ private static Route getAlternativeAmphibRoute(final GamePlayer player, final Ga private List calculateNonCombat(final GameData data, final GamePlayer player) { final Collection territories = data.getMap().getTerritories(); final List moves = movePlanesHomeNonCombat(player, data); + // these are the units we can move + final Predicate moveOfType = + Matches.unitIsOwnedBy(player) + .and(Matches.unitIsNotAa()) + // we can never move factories + .and(Matches.unitCanMove()) + .and(Matches.unitIsNotInfrastructure()) + .and(Matches.unitIsLand()); + final Predicate moveThrough = + Matches.territoryIsImpassable() + .negate() + .and(Matches.territoryIsNeutralButNotWater().negate()) + .and(Matches.territoryIsLand()); // move our units toward the nearest enemy capitol for (final Territory t : territories) { if (t.isWater()) { @@ -428,19 +448,6 @@ private List calculateNonCombat(final GameData data, final Game continue; } } - // these are the units we can move - final Predicate moveOfType = - Matches.unitIsOwnedBy(player) - .and(Matches.unitIsNotAa()) - // we can never move factories - .and(Matches.unitCanMove()) - .and(Matches.unitIsNotInfrastructure()) - .and(Matches.unitIsLand()); - final Predicate moveThrough = - Matches.territoryIsImpassable() - .negate() - .and(Matches.territoryIsNeutralButNotWater().negate()) - .and(Matches.territoryIsLand()); final List units = t.getUnitCollection().getMatches(moveOfType); if (units.isEmpty()) { continue; @@ -449,17 +456,17 @@ private List calculateNonCombat(final GameData data, final Game Territory to = null; // find the nearest enemy owned capital for (final GamePlayer otherPlayer : data.getPlayerList().getPlayers()) { - final Territory capitol = + final Territory capital = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital( otherPlayer, data.getMap()); - if (capitol != null - && !data.getRelationshipTracker().isAllied(player, capitol.getOwner())) { - final Route route = data.getMap().getRoute(t, capitol, moveThrough); - if (route != null && moveThrough.test(capitol)) { + if (capital != null + && !data.getRelationshipTracker().isAllied(player, capital.getOwner())) { + final Route route = data.getMap().getRoute(t, capital, moveThrough); + if (route != null && moveThrough.test(capital)) { final int distance = route.numberOfSteps(); if (distance != 0 && distance < minDistance) { minDistance = distance; - to = capitol; + to = capital; } } } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractMoveDelegate.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractMoveDelegate.java index 73b3eb2bb71..a52dbc82948 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractMoveDelegate.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractMoveDelegate.java @@ -8,8 +8,6 @@ import games.strategy.engine.data.Unit; import games.strategy.engine.posted.game.pbem.PbemMessagePoster; import games.strategy.triplea.delegate.battle.BattleTracker; -import games.strategy.triplea.delegate.data.MoveValidationResult; -import games.strategy.triplea.delegate.move.validation.MoveValidator; import games.strategy.triplea.delegate.remote.IMoveDelegate; import java.io.Serializable; import java.util.ArrayList; @@ -62,9 +60,8 @@ public Serializable saveState() { public void loadState(final Serializable state) { final AbstractMoveExtendedDelegateState s = (AbstractMoveExtendedDelegateState) state; super.loadState(s.superState); - // if the undo state wasnt saved, then dont load it. prevents overwriting undo state when we - // restore from an undo - // move + // if the undo state wasn't saved, then don't load it. prevents overwriting undo state when we + // restore from an undo move if (s.movesToUndo != null) { movesToUndo = s.movesToUndo; } @@ -120,19 +117,6 @@ public String move(final Collection units, final Route route) { @Override public abstract String performMove(MoveDescription move); - public static MoveValidationResult validateMove( - final GameData gameData, - final MoveType moveType, - final MoveDescription move, - final GamePlayer player, - final boolean isNonCombat, - final List undoableMoves) { - if (moveType == MoveType.SPECIAL) { - return SpecialMoveDelegate.validateMove(gameData, move.getUnits(), move.getRoute(), player); - } - return new MoveValidator(gameData).validateMove(move, player, isNonCombat, undoableMoves); - } - @Override public Collection getTerritoriesWhereAirCantLand(final GamePlayer player) { return new AirThatCantLandUtil(bridge).getTerritoriesWhereAirCantLand(player); diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java index a0899350833..d8ba865ccfa 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java @@ -1530,11 +1530,6 @@ public static Predicate unitOwnerHasImprovedArtillerySupportTech() { return u -> TechTracker.hasImprovedArtillerySupport(u.getOwner()); } - public static Predicate territoryHasNonAllowedCanal( - final GamePlayer player, final GameData gameData) { - return t -> new MoveValidator(gameData).validateCanal(new Route(t), null, player) != null; - } - public static Predicate territoryIsBlockedSea( final GamePlayer player, final GameProperties properties, diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/MoveDelegate.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/MoveDelegate.java index 5fbbd3839b0..f2c47b7123f 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/MoveDelegate.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/MoveDelegate.java @@ -619,9 +619,8 @@ public String performMove(final MoveDescription move) { // the current player final GamePlayer player = getUnitsOwner(move.getUnits()); final MoveValidationResult result = - new MoveValidator(data) - .validateMove( - move, player, GameStepPropertiesHelper.isNonCombatMove(data, false), movesToUndo); + new MoveValidator(data, GameStepPropertiesHelper.isNonCombatMove(data, false)) + .validateMove(move, player, movesToUndo); final StringBuilder errorMsg = new StringBuilder(100); final int numProblems = result.getTotalWarningCount() - (result.hasError() ? 0 : 1); final String numErrorsMsg = diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/SpecialMoveDelegate.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/SpecialMoveDelegate.java index 003cfd7f57a..aa753f1defa 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/SpecialMoveDelegate.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/SpecialMoveDelegate.java @@ -17,17 +17,12 @@ import games.strategy.triplea.attachments.TechAbilityAttachment; import games.strategy.triplea.delegate.battle.BattleDelegate; import games.strategy.triplea.delegate.battle.BattleTracker; -import games.strategy.triplea.delegate.battle.IBattle; -import games.strategy.triplea.delegate.battle.IBattle.BattleType; import games.strategy.triplea.delegate.data.MoveValidationResult; import games.strategy.triplea.delegate.move.validation.MoveValidator; import games.strategy.triplea.formatter.MyFormatter; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.Set; -import java.util.function.Predicate; import org.triplea.java.collections.CollectionUtils; import org.triplea.java.collections.IntegerMap; @@ -96,7 +91,7 @@ public String performMove(final MoveDescription move) { if (!allowAirborne(player, getData())) { return "No Airborne Movement Allowed Yet"; } - final GameState data = getData(); + final GameData data = getData(); final Collection units = move.getUnits(); final Route route = move.getRoute(); // there reason we use this, is because if we are in edit mode, we may have a different unit @@ -104,7 +99,8 @@ public String performMove(final MoveDescription move) { // player. final GamePlayer player = getUnitsOwner(units); // here we have our own new validation method.... - final MoveValidationResult result = validateMove(getData(), units, route, player); + final MoveValidationResult result = + new MoveValidator(data, false).validateSpecialMove(move, player); final StringBuilder errorMsg = new StringBuilder(100); final int numProblems = result.getTotalWarningCount() - (result.hasError() ? 0 : 1); final String numErrorsMsg = @@ -149,7 +145,10 @@ public String performMove(final MoveDescription move) { currentMove.addChange(airborneChange); // make the bases start filling up their capacity final Collection basesAtStart = - route.getStart().getUnitCollection().getMatches(getAirborneBaseMatch(player, data)); + route + .getStart() + .getUnitCollection() + .getMatches(MoveValidator.getAirborneBaseMatch(player, data)); final Change fillLaunchCapacity = getNewAssignmentOfNumberLaunchedChange(units.size(), basesAtStart, player, data); currentMove.addChange(fillLaunchCapacity); @@ -171,177 +170,6 @@ public String performMove(final MoveDescription move) { return null; } - static MoveValidationResult validateMove( - final GameData gameData, - final Collection units, - final Route route, - final GamePlayer player) { - final MoveValidationResult result = new MoveValidationResult(); - if (route.hasNoSteps()) { - return result; - } - if (new MoveValidator(gameData).validateFirst(units, route, player, result).getError() - != null) { - return result; - } - if (new MoveValidator(gameData).validateFuel(units, route, player, result).getError() != null) { - return result; - } - final boolean isEditMode = getEditMode(player.getData()); - if (!isEditMode) { - // make sure all units are at least friendly - for (final Unit unit : - CollectionUtils.getMatches(units, Matches.unitIsOwnedBy(player).negate())) { - result.addDisallowedUnit("Can only move owned units", unit); - } - } - if (validateAirborneMovements(units, route, player, result).getError() != null) { - return result; - } - return result; - } - - private static MoveValidationResult validateAirborneMovements( - final Collection units, - final Route route, - final GamePlayer player, - final MoveValidationResult result) { - final GameData data = player.getData(); - if (!TechAbilityAttachment.getAllowAirborneForces( - TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier()))) { - return result.setErrorReturnResult("Do Not Have Airborne Tech"); - } - final int airborneDistance = - TechAbilityAttachment.getAirborneDistance( - TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); - final Set airborneBases = - TechAbilityAttachment.getAirborneBases( - TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); - final Set airborneTypes = - TechAbilityAttachment.getAirborneTypes( - TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); - if (airborneDistance <= 0 || airborneBases.isEmpty() || airborneTypes.isEmpty()) { - return result.setErrorReturnResult("Require Airborne Forces And Launch Capacity Tech"); - } - if (route.numberOfSteps() > airborneDistance) { - return result.setErrorReturnResult("Destination Is Out Of Range"); - } - final Collection alliesForBases = - data.getRelationshipTracker().getAllies(player, true); - final Predicate airborneBaseMatch = getAirborneMatch(airborneBases, alliesForBases); - final Territory start = route.getStart(); - final Territory end = route.getEnd(); - final Collection basesAtStart = start.getUnitCollection().getMatches(airborneBaseMatch); - if (basesAtStart.isEmpty()) { - return result.setErrorReturnResult("Require Airborne Base At Originating Territory"); - } - - final int airborneCapacity = - TechAbilityAttachment.getAirborneCapacity( - basesAtStart, TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); - if (airborneCapacity <= 0) { - return result.setErrorReturnResult("Airborne Bases Must Have Launch Capacity"); - } else if (airborneCapacity < units.size()) { - final Collection overMax = new ArrayList<>(units); - overMax.removeAll(CollectionUtils.getNMatches(units, airborneCapacity, it -> true)); - for (final Unit u : overMax) { - result.addDisallowedUnit("Airborne Base Capacity Has Been Reached", u); - } - } - final Collection airborne = new ArrayList<>(); - for (final Unit u : units) { - if (!Matches.unitIsOwnedBy(player).test(u)) { - result.addDisallowedUnit("Must Own All Airborne Forces", u); - } else if (!Matches.unitIsOfTypes(airborneTypes).test(u)) { - result.addDisallowedUnit("Can Only Launch Airborne Forces", u); - } else if (Matches.unitIsDisabled().test(u)) { - result.addDisallowedUnit("Must Not Be Disabled", u); - } else if (!Matches.unitHasNotMoved().test(u)) { - result.addDisallowedUnit("Must Not Have Previously Moved Airborne Forces", u); - } else if (Matches.unitIsAirborne().test(u)) { - result.addDisallowedUnit("Cannot Move Units Already Airborne", u); - } else { - airborne.add(u); - } - } - if (airborne.isEmpty()) { - return result; - } - final BattleTracker battleTracker = AbstractMoveDelegate.getBattleTracker(data); - final boolean onlyWhereUnderAttackAlready = - Properties.getAirborneAttacksOnlyInExistingBattles(data.getProperties()); - final boolean onlyEnemyTerritories = - Properties.getAirborneAttacksOnlyInEnemyTerritories(data.getProperties()); - final List steps = route.getSteps(); - if (steps.isEmpty() - || !steps.stream() - .allMatch(Matches.territoryIsPassableAndNotRestricted(player, data.getProperties()))) { - return result.setErrorReturnResult("May Not Fly Over Impassable or Restricted Territories"); - } - if (steps.isEmpty() - || !steps.stream() - .allMatch( - Matches.territoryAllowsCanMoveAirUnitsOverOwnedLand( - player, data.getRelationshipTracker()))) { - return result.setErrorReturnResult("May Only Fly Over Territories Where Air May Move"); - } - final boolean someLand = airborne.stream().anyMatch(Matches.unitIsLand()); - final boolean someSea = airborne.stream().anyMatch(Matches.unitIsSea()); - final boolean land = Matches.territoryIsLand().test(end); - final boolean sea = Matches.territoryIsWater().test(end); - if (someLand && someSea) { - return result.setErrorReturnResult("Cannot Mix Land and Sea Units"); - } else if (someLand) { - if (!land) { - return result.setErrorReturnResult("Cannot Move Land Units To Sea"); - } - } else if (someSea && !sea) { - return result.setErrorReturnResult("Cannot Move Sea Units To Land"); - } - if (onlyWhereUnderAttackAlready) { - if (!battleTracker.getConquered().contains(end)) { - final IBattle battle = battleTracker.getPendingBattle(end, BattleType.NORMAL); - if (battle == null) { - return result.setErrorReturnResult( - "Airborne May Only Attack Territories Already Under Assault"); - } else if (land - && someLand - && battle.getAttackingUnits().stream().noneMatch(Matches.unitIsLand())) { - return result.setErrorReturnResult( - "Battle Must Have Some Land Units Participating Already"); - } else if (sea - && someSea - && battle.getAttackingUnits().stream().noneMatch(Matches.unitIsSea())) { - return result.setErrorReturnResult( - "Battle Must Have Some Sea Units Participating Already"); - } - } - } else if (onlyEnemyTerritories - && !(Matches.isTerritoryEnemyAndNotUnownedWater(player, data.getRelationshipTracker()) - .test(end) - || Matches.territoryHasEnemyUnits(player, data.getRelationshipTracker()).test(end))) { - return result.setErrorReturnResult("Destination Must Be Enemy Or Contain Enemy Units"); - } - return result; - } - - private static Predicate getAirborneBaseMatch( - final GamePlayer player, final GameState data) { - return getAirborneMatch( - TechAbilityAttachment.getAirborneBases( - TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())), - data.getRelationshipTracker().getAllies(player, true)); - } - - private static Predicate getAirborneMatch( - final Set types, final Collection unitOwners) { - return Matches.unitIsOwnedByAnyOf(unitOwners) - .and(Matches.unitIsOfTypes(types)) - .and(Matches.unitIsNotDisabled()) - .and(Matches.unitHasNotMoved()) - .and(Matches.unitIsAirborne().negate()); - } - private static Change getNewAssignmentOfNumberLaunchedChange( final int initialNewNumberLaunched, final Collection bases, @@ -395,15 +223,12 @@ private static boolean allowAirborne(final GamePlayer player, final GameState da final Collection territoriesWeCanLaunchFrom = CollectionUtils.getMatches( map.getTerritories(), - Matches.territoryHasUnitsThatMatch(getAirborneMatch(airborneBases, alliesForBases))); + Matches.territoryHasUnitsThatMatch( + MoveValidator.getAirborneMatch(airborneBases, alliesForBases))); return !territoriesWeCanLaunchFrom.isEmpty(); } - private static boolean getEditMode(final GameState data) { - return BaseEditDelegate.getEditMode(data.getProperties()); - } - @Override public int pusAlreadyLost(final Territory t) { return 0; diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java index 3dcc576c550..37b4c6c6314 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java @@ -249,7 +249,7 @@ public Change addAttackChange( } // TODO: This checks for ignored sub/trns and skips the set of the attackers to 0 movement left // If attacker stops in an occupied territory, movement stops (battle is optional) - if (new MoveValidator(gameData).onlyIgnoredUnitsOnPath(route, attacker, false)) { + if (new MoveValidator(gameData, false).onlyIgnoredUnitsOnPath(route, attacker, false)) { return change; } change.add(ChangeFactory.markNoMovementChange(nonAir)); diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/retreat/DefensiveSubsRetreat.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/retreat/DefensiveSubsRetreat.java index f44547d39d7..323f3dc9465 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/retreat/DefensiveSubsRetreat.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/retreat/DefensiveSubsRetreat.java @@ -132,7 +132,7 @@ public Collection getEmptyOrFriendlySeaNeighbors() { final Predicate canalMatch = t -> { final Route r = new Route(battleState.getBattleSite(), t); - return new MoveValidator(battleState.getGameData()) + return new MoveValidator(battleState.getGameData(), false) .validateCanal(r, unitsToRetreat, battleState.getPlayer(DEFENSE)) == null; }; diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/move/validation/MoveValidator.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/move/validation/MoveValidator.java index de2d49f8d06..78bf5d37491 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/move/validation/MoveValidator.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/move/validation/MoveValidator.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableMap; import games.strategy.engine.data.GameData; import games.strategy.engine.data.GamePlayer; +import games.strategy.engine.data.GameState; import games.strategy.engine.data.MoveDescription; import games.strategy.engine.data.RelationshipTracker; import games.strategy.engine.data.ResourceCollection; @@ -16,6 +17,7 @@ import games.strategy.triplea.attachments.CanalAttachment; import games.strategy.triplea.attachments.PlayerAttachment; import games.strategy.triplea.attachments.RulesAttachment; +import games.strategy.triplea.attachments.TechAbilityAttachment; import games.strategy.triplea.attachments.TechAttachment; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.AbstractMoveDelegate; @@ -23,10 +25,13 @@ import games.strategy.triplea.delegate.GameStepPropertiesHelper; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.MoveDelegate; +import games.strategy.triplea.delegate.TechTracker; import games.strategy.triplea.delegate.TerritoryEffectHelper; import games.strategy.triplea.delegate.TransportTracker; import games.strategy.triplea.delegate.UndoableMove; import games.strategy.triplea.delegate.UnitComparator; +import games.strategy.triplea.delegate.battle.BattleTracker; +import games.strategy.triplea.delegate.battle.IBattle; import games.strategy.triplea.delegate.data.MoveValidationResult; import games.strategy.triplea.delegate.data.MustMoveWithDetails; import games.strategy.triplea.formatter.MyFormatter; @@ -45,11 +50,13 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.Nullable; +import lombok.AllArgsConstructor; import org.triplea.java.PredicateBuilder; import org.triplea.java.collections.CollectionUtils; import org.triplea.java.collections.IntegerMap; -/** Provides some static methods for validating movement. */ +/** Responsible for validating unit movement. */ +@AllArgsConstructor public class MoveValidator { public static final String TRANSPORT_HAS_ALREADY_UNLOADED_UNITS_IN_A_PREVIOUS_PHASE = @@ -78,17 +85,15 @@ public class MoveValidator { public static final String NOT_ALL_UNITS_CAN_BLITZ = "Not all units can blitz"; private final GameData data; + private final boolean isNonCombat; - public MoveValidator(final GameData data) { - this.data = data; + public MoveValidationResult validateMove(final MoveDescription move, final GamePlayer player) { + return validateMove(move, player, List.of()); } /** Validates the specified move. */ public MoveValidationResult validateMove( - final MoveDescription move, - final GamePlayer player, - final boolean isNonCombat, - final List undoableMoves) { + final MoveDescription move, final GamePlayer player, final List undoableMoves) { final Collection units = move.getUnits(); final Route route = move.getRoute(); final Map unitsToTransports = move.getUnitsToTransports(); @@ -167,7 +172,7 @@ public MoveValidationResult validateMove( return result; } - public MoveValidationResult validateFirst( + private MoveValidationResult validateFirst( final Collection units, final Route route, final GamePlayer player, @@ -243,7 +248,7 @@ public MoveValidationResult validateFirst( return result; } - public MoveValidationResult validateFuel( + private MoveValidationResult validateFuel( final Collection units, final Route route, final GamePlayer player, @@ -280,11 +285,12 @@ private MoveValidationResult validateCanal( * * @param units (Can be null. If null we will assume all units would be stopped by the canal.) */ - public String validateCanal( + public @Nullable String validateCanal( final Route route, @Nullable final Collection units, final GamePlayer player) { return validateCanal(route, units, new HashMap<>(), player); } + @Nullable String validateCanal( final Route route, @Nullable final Collection units, @@ -897,8 +903,8 @@ private Set checkLandTransports( final Collection possibleLandTransports, final Set unitsToLandTransport) { final Set disallowedUnits = new HashSet<>(); - data.acquireReadLock(); - try { + + try (GameData.Unlocker ignored = data.acquireReadLock()) { int numLandTransportsWithoutCapacity = getNumLandTransportsWithoutCapacity(possibleLandTransports, player); final IntegerMap landTransportsWithCapacity = @@ -924,8 +930,6 @@ private Set checkLandTransports( disallowedUnits.add(unit); } } - } finally { - data.releaseReadLock(); } return disallowedUnits; } @@ -1884,4 +1888,170 @@ private static int getNeutralCharge( final GameProperties properties, final int numberOfTerritories) { return numberOfTerritories * Properties.getNeutralCharge(properties); } + + public MoveValidationResult validateSpecialMove( + final MoveDescription move, final GamePlayer player) { + final Collection units = move.getUnits(); + final Route route = move.getRoute(); + final MoveValidationResult result = new MoveValidationResult(); + if (validateFirst(units, route, player, result).getError() != null) { + return result; + } + if (validateFuel(move.getUnits(), move.getRoute(), player, result).getError() != null) { + return result; + } + final boolean isEditMode = getEditMode(data.getProperties()); + if (!isEditMode) { + // make sure all units are at least friendly + for (final Unit unit : + CollectionUtils.getMatches(units, Matches.unitIsOwnedBy(player).negate())) { + result.addDisallowedUnit("Can only move owned units", unit); + } + } + if (validateAirborneMovements(units, route, player, result).getError() != null) { + return result; + } + return result; + } + + private static MoveValidationResult validateAirborneMovements( + final Collection units, + final Route route, + final GamePlayer player, + final MoveValidationResult result) { + final GameData data = player.getData(); + if (!TechAbilityAttachment.getAllowAirborneForces( + TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier()))) { + return result.setErrorReturnResult("Do Not Have Airborne Tech"); + } + final int airborneDistance = + TechAbilityAttachment.getAirborneDistance( + TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); + final Set airborneBases = + TechAbilityAttachment.getAirborneBases( + TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); + final Set airborneTypes = + TechAbilityAttachment.getAirborneTypes( + TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); + if (airborneDistance <= 0 || airborneBases.isEmpty() || airborneTypes.isEmpty()) { + return result.setErrorReturnResult("Require Airborne Forces And Launch Capacity Tech"); + } + if (route.numberOfSteps() > airborneDistance) { + return result.setErrorReturnResult("Destination Is Out Of Range"); + } + final Collection alliesForBases = + data.getRelationshipTracker().getAllies(player, true); + final Predicate airborneBaseMatch = getAirborneMatch(airborneBases, alliesForBases); + final Territory start = route.getStart(); + final Territory end = route.getEnd(); + final Collection basesAtStart = start.getUnitCollection().getMatches(airborneBaseMatch); + if (basesAtStart.isEmpty()) { + return result.setErrorReturnResult("Require Airborne Base At Originating Territory"); + } + + final int airborneCapacity = + TechAbilityAttachment.getAirborneCapacity( + basesAtStart, TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())); + if (airborneCapacity <= 0) { + return result.setErrorReturnResult("Airborne Bases Must Have Launch Capacity"); + } else if (airborneCapacity < units.size()) { + final Collection overMax = new ArrayList<>(units); + overMax.removeAll(CollectionUtils.getNMatches(units, airborneCapacity, it -> true)); + for (final Unit u : overMax) { + result.addDisallowedUnit("Airborne Base Capacity Has Been Reached", u); + } + } + final Collection airborne = new ArrayList<>(); + for (final Unit u : units) { + if (!Matches.unitIsOwnedBy(player).test(u)) { + result.addDisallowedUnit("Must Own All Airborne Forces", u); + } else if (!Matches.unitIsOfTypes(airborneTypes).test(u)) { + result.addDisallowedUnit("Can Only Launch Airborne Forces", u); + } else if (Matches.unitIsDisabled().test(u)) { + result.addDisallowedUnit("Must Not Be Disabled", u); + } else if (!Matches.unitHasNotMoved().test(u)) { + result.addDisallowedUnit("Must Not Have Previously Moved Airborne Forces", u); + } else if (Matches.unitIsAirborne().test(u)) { + result.addDisallowedUnit("Cannot Move Units Already Airborne", u); + } else { + airborne.add(u); + } + } + if (airborne.isEmpty()) { + return result; + } + final BattleTracker battleTracker = AbstractMoveDelegate.getBattleTracker(data); + final boolean onlyWhereUnderAttackAlready = + Properties.getAirborneAttacksOnlyInExistingBattles(data.getProperties()); + final boolean onlyEnemyTerritories = + Properties.getAirborneAttacksOnlyInEnemyTerritories(data.getProperties()); + final List steps = route.getSteps(); + if (steps.isEmpty() + || !steps.stream() + .allMatch(Matches.territoryIsPassableAndNotRestricted(player, data.getProperties()))) { + return result.setErrorReturnResult("May Not Fly Over Impassable or Restricted Territories"); + } + if (steps.isEmpty() + || !steps.stream() + .allMatch( + Matches.territoryAllowsCanMoveAirUnitsOverOwnedLand( + player, data.getRelationshipTracker()))) { + return result.setErrorReturnResult("May Only Fly Over Territories Where Air May Move"); + } + final boolean someLand = airborne.stream().anyMatch(Matches.unitIsLand()); + final boolean someSea = airborne.stream().anyMatch(Matches.unitIsSea()); + final boolean land = Matches.territoryIsLand().test(end); + final boolean sea = Matches.territoryIsWater().test(end); + if (someLand && someSea) { + return result.setErrorReturnResult("Cannot Mix Land and Sea Units"); + } else if (someLand) { + if (!land) { + return result.setErrorReturnResult("Cannot Move Land Units To Sea"); + } + } else if (someSea && !sea) { + return result.setErrorReturnResult("Cannot Move Sea Units To Land"); + } + if (onlyWhereUnderAttackAlready) { + if (!battleTracker.getConquered().contains(end)) { + final IBattle battle = battleTracker.getPendingBattle(end, IBattle.BattleType.NORMAL); + if (battle == null) { + return result.setErrorReturnResult( + "Airborne May Only Attack Territories Already Under Assault"); + } else if (land + && someLand + && battle.getAttackingUnits().stream().noneMatch(Matches.unitIsLand())) { + return result.setErrorReturnResult( + "Battle Must Have Some Land Units Participating Already"); + } else if (sea + && someSea + && battle.getAttackingUnits().stream().noneMatch(Matches.unitIsSea())) { + return result.setErrorReturnResult( + "Battle Must Have Some Sea Units Participating Already"); + } + } + } else if (onlyEnemyTerritories + && !(Matches.isTerritoryEnemyAndNotUnownedWater(player, data.getRelationshipTracker()) + .test(end) + || Matches.territoryHasEnemyUnits(player, data.getRelationshipTracker()).test(end))) { + return result.setErrorReturnResult("Destination Must Be Enemy Or Contain Enemy Units"); + } + return result; + } + + public static Predicate getAirborneBaseMatch( + final GamePlayer player, final GameState data) { + return getAirborneMatch( + TechAbilityAttachment.getAirborneBases( + TechTracker.getCurrentTechAdvances(player, data.getTechnologyFrontier())), + data.getRelationshipTracker().getAllies(player, true)); + } + + public static Predicate getAirborneMatch( + final Set types, final Collection unitOwners) { + return Matches.unitIsOwnedByAnyOf(unitOwners) + .and(Matches.unitIsOfTypes(types)) + .and(Matches.unitIsNotDisabled()) + .and(Matches.unitHasNotMoved()) + .and(Matches.unitIsAirborne().negate()); + } } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panel/move/MovableUnitsFilter.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panel/move/MovableUnitsFilter.java index 63982953e05..ca6dc6456b1 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panel/move/MovableUnitsFilter.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panel/move/MovableUnitsFilter.java @@ -8,7 +8,6 @@ import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitCollection; import games.strategy.triplea.attachments.TechAttachment; -import games.strategy.triplea.delegate.AbstractMoveDelegate; import games.strategy.triplea.delegate.AbstractMoveDelegate.MoveType; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.UndoableMove; @@ -230,8 +229,12 @@ private MoveValidationResultWithDependents validateMoveWithDependents( : TransportUtils.mapTransports(route, units, transportsToLoad); final MoveDescription move = new MoveDescription(unitsWithDependents, route, unitsToTransports, dependentUnits); - result = - AbstractMoveDelegate.validateMove(data, moveType, move, player, nonCombat, undoableMoves); + final MoveValidator moveValidator = new MoveValidator(data, nonCombat); + if (moveType == MoveType.SPECIAL) { + result = moveValidator.validateSpecialMove(move, player); + } else { + result = moveValidator.validateMove(move, player, undoableMoves); + } } finally { data.releaseReadLock(); } diff --git a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/WW2V3Year41Test.java b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/WW2V3Year41Test.java index c82e03713ed..6034fc6fbaa 100644 --- a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/WW2V3Year41Test.java +++ b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/WW2V3Year41Test.java @@ -1488,8 +1488,8 @@ void testParatroopsWalkOnWater() { france.getUnitCollection().getMatches(Matches.unitIsAirTransportable()); assertFalse(paratroopers.isEmpty()); final MoveValidationResult results = - new MoveValidator(gameData) - .validateMove(new MoveDescription(paratroopers, r), germans, false, null); + new MoveValidator(gameData, false) + .validateMove(new MoveDescription(paratroopers, r), germans); assertFalse(results.isMoveValid()); } @@ -1510,8 +1510,7 @@ void testBomberWithTankOverWaterParatroopers() { toMove.addAll(germany.getUnitCollection().getMatches(Matches.unitIsStrategicBomber())); assertEquals(2, toMove.size()); final MoveValidationResult results = - new MoveValidator(gameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + new MoveValidator(gameData, false).validateMove(new MoveDescription(toMove, r), germans); assertFalse(results.isMoveValid()); } @@ -1532,8 +1531,7 @@ void testBomberTankOverWater() { toMove.addAll(germany.getUnitCollection().getMatches(Matches.unitIsStrategicBomber())); assertEquals(2, toMove.size()); final MoveValidationResult results = - new MoveValidator(gameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + new MoveValidator(gameData, false).validateMove(new MoveDescription(toMove, r), germans); assertFalse(results.isMoveValid()); } diff --git a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/move/validation/MoveValidatorTest.java b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/move/validation/MoveValidatorTest.java index 409852c5b38..9de27cce000 100644 --- a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/move/validation/MoveValidatorTest.java +++ b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/move/validation/MoveValidatorTest.java @@ -117,47 +117,38 @@ void testValidateMoveForRequiresUnitsToMove() { final Territory easternGermany = territory("Eastern Germany", twwGameData); final Route r = new Route(berlin, easternGermany); List toMove = berlin.getUnitCollection().getMatches(Matches.unitCanMove()); + + MoveValidator moveValidator = new MoveValidator(twwGameData, false); MoveValidationResult results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertTrue(results.isMoveValid()); // Add germanTrain to units which fails since it requires germainRail addTo(berlin, GameDataTestUtil.germanTrain(twwGameData).create(1, germans)); toMove = berlin.getUnitCollection().getMatches(Matches.unitCanMove()); - results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + results = moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertFalse(results.isMoveValid()); // Add germanRail to only destination so it fails final Collection germanRail = GameDataTestUtil.germanRail(twwGameData).create(1, germans); addTo(easternGermany, germanRail); - results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + results = moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertFalse(results.isMoveValid()); // Add germanRail to start so move succeeds addTo(berlin, GameDataTestUtil.germanRail(twwGameData).create(1, germans)); - results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + results = moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertTrue(results.isMoveValid()); // Remove germanRail from destination so move fails GameDataTestUtil.removeFrom(easternGermany, germanRail); - results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + results = moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertFalse(results.isMoveValid()); // Add allied owned germanRail to destination so move succeeds final GamePlayer japan = GameDataTestUtil.japan(twwGameData); addTo(easternGermany, GameDataTestUtil.germanRail(twwGameData).create(1, japan)); - results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(toMove, r), germans, false, null); + results = moveValidator.validateMove(new MoveDescription(toMove, r), germans); assertTrue(results.isMoveValid()); } @@ -174,44 +165,40 @@ void testValidateMoveForLandTransports() { berlin.getUnitCollection().clear(); GameDataTestUtil.truck(twwGameData).create(1, germans); addTo(berlin, GameDataTestUtil.truck(twwGameData).create(1, germans)); + + MoveValidator moveValidator = new MoveValidator(twwGameData, true); MoveValidationResult results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertTrue(results.isMoveValid()); // Add an infantry for truck to transport addTo(berlin, GameDataTestUtil.germanInfantry(twwGameData).create(1, germans)); results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertTrue(results.isMoveValid()); // Add an infantry and the truck can't transport both addTo(berlin, GameDataTestUtil.germanInfantry(twwGameData).create(1, germans)); results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertFalse(results.isMoveValid()); // Add a large truck (has capacity for 2 infantry) to transport second infantry addTo(berlin, GameDataTestUtil.largeTruck(twwGameData).create(1, germans)); results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertTrue(results.isMoveValid()); // Add an infantry that the large truck can also transport addTo(berlin, GameDataTestUtil.germanInfantry(twwGameData).create(1, germans)); results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertTrue(results.isMoveValid()); // Add an infantry that can't be transported addTo(berlin, GameDataTestUtil.germanInfantry(twwGameData).create(1, germans)); results = - new MoveValidator(twwGameData) - .validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans, true, null); + moveValidator.validateMove(new MoveDescription(berlin.getUnitCollection(), r), germans); assertFalse(results.isMoveValid()); } @@ -229,13 +216,12 @@ void testValidateUnitsCanLoadInHostileSeaZones() { final List transport = sz27.getUnitCollection().getMatches(Matches.unitIsTransport()); Map unitsToTransports = TransportUtils.mapTransports(r, northernGermany.getUnitCollection(), transport); + + MoveValidator moveValidator = new MoveValidator(twwGameData, false); MoveValidationResult results = - new MoveValidator(twwGameData) - .validateMove( - new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), - germans, - false, - null); + moveValidator.validateMove( + new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), + germans); assertTrue(results.isMoveValid()); // Add USA ship to transport sea zone @@ -244,12 +230,9 @@ void testValidateUnitsCanLoadInHostileSeaZones() { unitsToTransports = TransportUtils.mapTransports(r, northernGermany.getUnitCollection(), transport); results = - new MoveValidator(twwGameData) - .validateMove( - new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), - germans, - false, - null); + moveValidator.validateMove( + new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), + germans); assertFalse(results.isMoveValid()); // Set 'Units Can Load In Hostile Sea Zones' to true @@ -257,12 +240,9 @@ void testValidateUnitsCanLoadInHostileSeaZones() { unitsToTransports = TransportUtils.mapTransports(r, northernGermany.getUnitCollection(), transport); results = - new MoveValidator(twwGameData) - .validateMove( - new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), - germans, - false, - null); + moveValidator.validateMove( + new MoveDescription(northernGermany.getUnitCollection(), r, unitsToTransports), + germans); assertTrue(results.isMoveValid()); } }