From bd9d2cb5088ba2fb9157f09a5a24730aa185ff2a Mon Sep 17 00:00:00 2001 From: frigoref Date: Sat, 27 Jul 2024 12:33:30 +0200 Subject: [PATCH] Issue 12447 part2 (#12783) * Reapply "Issues 12447 (Standardized Sorting of UnitCategory) (#12722)" (#12762) This reverts commit e73fdec1d452793d89ffd8c8c1616ff6b286219f. * Issue_12447_Part2 #1 PlayerUnitsPanel.java - ensure uniqueness in returned list in method getAllUnitCategories - reduce complexity by use of new method getProducibleUnitTypes ProductionFrontier.java - new method getProducibleUnitTypes --- .../engine/data/ProductionFrontier.java | 16 ++ .../calculator/BattleCalculatorPanel.java | 38 ++-- .../calculator/OrderOfLossesInputPanel.java | 8 +- .../odds/calculator/PlayerUnitsPanel.java | 108 ++++-------- .../strategy/triplea/ui/BattleDisplay.java | 19 +- .../strategy/triplea/ui/BattleModel.java | 3 +- .../strategy/triplea/ui/SimpleUnitPanel.java | 12 +- .../strategy/triplea/ui/UnitChooser.java | 20 ++- .../triplea/ui/panels/map/MapPanel.java | 7 +- .../ui/unit/scroller/AvatarPanelFactory.java | 36 ++-- .../ui/unit/scroller/UnitScroller.java | 2 +- .../strategy/triplea/util/UnitSeparator.java | 163 +++++++++++++++--- .../ui/AbstractUndoableMovesPanel.java | 4 +- 13 files changed, 280 insertions(+), 156 deletions(-) diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/ProductionFrontier.java b/game-app/game-core/src/main/java/games/strategy/engine/data/ProductionFrontier.java index c0dbaf828b..d0243aa203 100644 --- a/game-app/game-core/src/main/java/games/strategy/engine/data/ProductionFrontier.java +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/ProductionFrontier.java @@ -3,6 +3,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -50,6 +51,21 @@ public List getRules() { return cachedRules; } + /** + * @return Collection of UnitType that can be produced by this frontier + */ + public Collection getProducibleUnitTypes() { + Collection producibleUnitTypes = new ArrayList<>(); + for (final ProductionRule rule : this) { + for (final NamedAttachable type : rule.getResults().keySet()) { + if (type instanceof UnitType) { + producibleUnitTypes.add((UnitType) type); + } + } + } + return producibleUnitTypes; + } + @Override public Iterator iterator() { return getRules().iterator(); diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/BattleCalculatorPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/BattleCalculatorPanel.java index ff3c238602..c4c61f1a3b 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/BattleCalculatorPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/BattleCalculatorPanel.java @@ -417,12 +417,17 @@ class BattleCalculatorPanel extends JPanel { Matches.unitIsOwnedBy(getDefender()) .and( Matches.unitCanBeInBattle( - true, isLand(), 1, hasMaxRounds(isLand(), data), true, List.of()))); + true, + isLandBattle(), + 1, + hasMaxRounds(isLandBattle(), data), + true, + List.of()))); final GamePlayer newDefender = getAttacker(); final List newDefenders = CollectionUtils.getMatches( attackingUnitsPanel.getUnits(), - Matches.unitCanBeInBattle(true, isLand(), 1, true)); + Matches.unitCanBeInBattle(true, isLandBattle(), 1, true)); setAttacker(newAttacker); setDefender(newDefender); setAttackingUnits(newAttackers); @@ -435,8 +440,8 @@ class BattleCalculatorPanel extends JPanel { new OrderOfLossesInputPanel( attackerOrderOfLosses, defenderOrderOfLosses, - attackingUnitsPanel.getCategories(), - defendingUnitsPanel.getCategories(), + attackingUnitsPanel.getUnitCategories(), + defendingUnitsPanel.getUnitCategories(), landBattleCheckBox.isSelected(), uiContext, data); @@ -559,12 +564,12 @@ private void updateStats() { try { final Territory location = findPotentialBattleSite(); if (location == null) { - throw new IllegalStateException("No territory found that is land:" + isLand()); + throw new IllegalStateException("No territory found that is land:" + isLandBattle()); } final List defending = defendingUnitsPanel.getUnits(); final List attacking = attackingUnitsPanel.getUnits(); List bombarding = new ArrayList<>(); - if (isLand()) { + if (isLandBattle()) { bombarding = CollectionUtils.getMatches(attacking, Matches.unitCanBombard(getAttacker())); attacking.removeAll(bombarding); @@ -622,7 +627,7 @@ private void updateStats() { attackerWin.setText(formatPercentage(results.getAttackerWinPercent())); defenderWin.setText(formatPercentage(results.getDefenderWinPercent())); draw.setText(formatPercentage(results.getDrawPercent())); - final boolean isLand = isLand(); + final boolean isLand = isLandBattle(); final List mainCombatAttackers = CollectionUtils.getMatches( attackers.get(), Matches.unitCanBeInBattle(true, isLand, 1, true)); @@ -658,9 +663,9 @@ private void updateStats() { private Territory findPotentialBattleSite() { Territory location = null; - if (this.location == null || this.location.isWater() == isLand()) { + if (this.location == null || this.location.isWater() == isLandBattle()) { for (final Territory t : data.getMap()) { - if (t.isWater() == !isLand()) { + if (t.isWater() == !isLandBattle()) { location = t; break; } @@ -693,8 +698,9 @@ private void setAttackingUnits(final List initialUnits) { CollectionUtils.getMatches( units, Matches.unitCanBeInBattle( - true, isLand(), 1, hasMaxRounds(isLand(), data), false, List.of())), - isLand()); + true, isLandBattle(), 1, hasMaxRounds(isLandBattle(), data), false, List.of())), + isLandBattle(), + location); } void addDefendingUnits(final List unitsToAdd) { @@ -708,8 +714,10 @@ private void setDefendingUnits(final List initialUnits) { final List units = Optional.ofNullable(initialUnits).orElseGet(List::of); defendingUnitsPanel.init( getDefender(), - CollectionUtils.getMatches(units, Matches.unitCanBeInBattle(false, isLand(), 1, false)), - isLand()); + CollectionUtils.getMatches( + units, Matches.unitCanBeInBattle(false, isLandBattle(), 1, false)), + isLandBattle(), + location); } public boolean hasAttackingUnitsAdded() { @@ -720,7 +728,7 @@ public boolean hasDefendingUnitsAdded() { return !defendingUnitsPanel.isEmpty(); } - private boolean isLand() { + private boolean isLandBattle() { return landBattleCheckBox.isSelected(); } @@ -742,7 +750,7 @@ private void setResultsToBlank() { private void setWidgetActivation() { keepOneAttackingLandUnitCheckBox.setEnabled(landBattleCheckBox.isSelected()); amphibiousCheckBox.setEnabled(landBattleCheckBox.isSelected()); - final boolean isLand = isLand(); + final boolean isLand = isLandBattle(); try (GameData.Unlocker ignored = data.acquireReadLock()) { final List attackers = CollectionUtils.getMatches( diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/OrderOfLossesInputPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/OrderOfLossesInputPanel.java index b24c0e1c55..d0e445ac78 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/OrderOfLossesInputPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/OrderOfLossesInputPanel.java @@ -12,6 +12,7 @@ import games.strategy.triplea.image.UnitImageFactory.ImageKey; import games.strategy.triplea.ui.UiContext; import games.strategy.triplea.util.UnitCategory; +import games.strategy.triplea.util.UnitSeparator; import java.awt.Color; import java.awt.Component; import java.util.ArrayList; @@ -252,12 +253,13 @@ private void layoutComponents() { } private JPanel getUnitButtonPanel( - final List categories, final JTextField textField) { + final List unitCategories, final JTextField textField) { final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - if (categories != null) { + if (unitCategories != null) { + UnitSeparator.sortUnitCategories(unitCategories, data); final Set typesUsed = new HashSet<>(); - for (final UnitCategory category : categories) { + for (final UnitCategory category : unitCategories) { // no duplicates or infrastructure allowed. no sea if land, no land if sea. if (typesUsed.contains(category.getType()) || Matches.unitTypeIsInfrastructure().test(category.getType()) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/PlayerUnitsPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/PlayerUnitsPanel.java index 42822b1ced..a6e420b188 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/PlayerUnitsPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/PlayerUnitsPanel.java @@ -2,13 +2,10 @@ import games.strategy.engine.data.GameData; import games.strategy.engine.data.GamePlayer; -import games.strategy.engine.data.NamedAttachable; import games.strategy.engine.data.ProductionFrontier; -import games.strategy.engine.data.ProductionRule; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; -import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.ui.UiContext; import games.strategy.triplea.util.TuvCostsCalculator; @@ -34,8 +31,8 @@ public class PlayerUnitsPanel extends JPanel { private final GameData data; private final UiContext uiContext; private final boolean defender; - private boolean isLand = true; - @Getter private List categories = null; + private boolean isLandBattle = true; + @Getter private List unitCategories = null; private final List listeners = new ArrayList<>(); private final List unitPanels = new ArrayList<>(); @@ -59,54 +56,17 @@ public List getUnits() { } /** Sets up components to an initial state. */ - public void init(final GamePlayer gamePlayer, final List units, final boolean land) { - isLand = land; + public void init( + final GamePlayer gamePlayer, + final List units, + final boolean isLandBattle, + final Territory territory) { + this.isLandBattle = isLandBattle; unitPanels.clear(); - categories = new ArrayList<>(categorize(gamePlayer, units)); - - categories.sort( - (c1, c2) -> { - if (!c1.isOwnedBy(c2.getOwner())) { - if (c1.isOwnedBy(gamePlayer)) { - return -1; - } else if (c2.isOwnedBy(gamePlayer)) { - return 1; - } else { - return c1.getOwner().getName().compareTo(c2.getOwner().getName()); - } - } - final UnitType ut1 = c1.getType(); - final UnitType ut2 = c2.getType(); - final UnitAttachment u1 = ut1.getUnitAttachment(); - final UnitAttachment u2 = ut2.getUnitAttachment(); - // For land battles, sort by land, air, can't combat move (AA), bombarding - if (land) { - if (u1.getIsSea() != u2.getIsSea()) { - return u1.getIsSea() ? 1 : -1; - } - final boolean u1CanNotCombatMove = - Matches.unitTypeCanNotMoveDuringCombatMove().test(ut1) - || !Matches.unitTypeCanMove(gamePlayer).test(ut1); - final boolean u2CanNotCombatMove = - Matches.unitTypeCanNotMoveDuringCombatMove().test(ut2) - || !Matches.unitTypeCanMove(gamePlayer).test(ut2); - if (u1CanNotCombatMove != u2CanNotCombatMove) { - return u1CanNotCombatMove ? 1 : -1; - } - if (u1.getIsAir() != u2.getIsAir()) { - return u1.getIsAir() ? 1 : -1; - } - } else { - if (u1.getIsSea() != u2.getIsSea()) { - return u1.getIsSea() ? -1 : 1; - } - } - return u1.getName().compareTo(u2.getName()); - }); removeAll(); final Predicate predicate; - if (land) { + if (isLandBattle) { if (defender) { predicate = Matches.unitTypeIsNotSea(); } else { @@ -120,9 +80,11 @@ public void init(final GamePlayer gamePlayer, final List units, final bool costs = new TuvCostsCalculator().getCostsForTuv(gamePlayer); } + unitCategories = getAllUnitCategories(gamePlayer, units); + UnitSeparator.sortUnitCategories(unitCategories, territory, gamePlayer); GamePlayer previousPlayer = null; JPanel panel = null; - for (final UnitCategory category : categories) { + for (final UnitCategory category : unitCategories) { if (predicate.test(category.getType())) { if (!category.isOwnedBy(previousPlayer)) { panel = new JPanel(); @@ -131,10 +93,12 @@ public void init(final GamePlayer gamePlayer, final List units, final bool add(panel); previousPlayer = category.getOwner(); } - final var unitPanel = new UnitPanel(uiContext, category, costs); - unitPanel.addChangeListener(this::notifyListeners); - panel.add(unitPanel); - unitPanels.add(unitPanel); + if (panel != null) { + final var unitPanel = new UnitPanel(uiContext, category, costs); + unitPanel.addChangeListener(this::notifyListeners); + panel.add(unitPanel); + unitPanels.add(unitPanel); + } } } @@ -150,10 +114,11 @@ public void init(final GamePlayer gamePlayer, final List units, final bool * production frontier and then any unit types the player owns on the map. Then populate the list * of units into the categories. */ - private Set categorize(final GamePlayer gamePlayer, final List units) { + private List getAllUnitCategories( + final GamePlayer gamePlayer, final List units) { // Get player unit types from production frontier and unit types on the map - final Set categories = new LinkedHashSet<>(); + final List categories = new ArrayList<>(); for (final UnitType t : getUnitTypes(gamePlayer)) { final UnitCategory category = new UnitCategory(t, gamePlayer); categories.add(category); @@ -172,35 +137,32 @@ private Set categorize(final GamePlayer gamePlayer, final List unitCategories = UnitSeparator.categorize(units); - for (final UnitCategory category : categories) { - for (final UnitCategory unitCategory : unitCategories) { - if (category.equals(unitCategory)) { - category.getUnits().addAll(unitCategory.getUnits()); - } + final Set unitCategoriesWithUnits = UnitSeparator.categorize(units); + for (final UnitCategory unitCategoryWithUnits : unitCategoriesWithUnits) { + int categoryIndex = categories.indexOf(unitCategoryWithUnits); + if (categoryIndex > 0) { + categories.get(categoryIndex).getUnits().addAll(unitCategoryWithUnits.getUnits()); + } else { + categories.add(unitCategoryWithUnits); } } - categories.addAll(unitCategories); return categories; } /** * Return all the unit types available for the given player. A unit type is available if the unit - * can be purchased or if a player has one on the map. + * can be produced/purchased or if a player has one on the map. */ private Collection getUnitTypes(final GamePlayer player) { Collection unitTypes = new LinkedHashSet<>(); + final ProductionFrontier frontier = player.getProductionFrontier(); if (frontier != null) { - for (final ProductionRule rule : frontier) { - for (final NamedAttachable type : rule.getResults().keySet()) { - if (type instanceof UnitType) { - unitTypes.add((UnitType) type); - } - } - } + unitTypes.addAll(frontier.getProducibleUnitTypes()); } + + // get any unit on the map and check for (final Territory t : data.getMap()) { for (final Unit u : t.getUnitCollection()) { if (u.isOwnedBy(player)) { @@ -216,10 +178,10 @@ private Collection getUnitTypes(final GamePlayer player) { unitTypes, Matches.unitTypeCanBeInBattle( !defender, - isLand, + isLandBattle, player, 1, - BattleCalculatorPanel.hasMaxRounds(isLand, data), + BattleCalculatorPanel.hasMaxRounds(isLandBattle, data), false, List.of())); diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleDisplay.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleDisplay.java index c375993c77..d949f18450 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleDisplay.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleDisplay.java @@ -47,6 +47,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.annotation.Nullable; import javax.swing.AbstractAction; import javax.swing.Action; @@ -230,10 +232,10 @@ private Collection updateKilledUnits( for (final Collection dependentCollection : dependentsMap.values()) { dependentUnitsReturned.addAll(dependentCollection); } - for (final UnitCategory category : - UnitSeparator.categorize( - killedUnits, - UnitSeparator.SeparatorCategories.builder().dependents(dependentsMap).build())) { + + List unitCategories = + UnitSeparator.getSortedUnitCategories(killedUnits, gameData, uiContext.getMapData()); + for (final UnitCategory category : unitCategories) { final JPanel panel = new JPanel(); JLabel unit = uiContext.newUnitImageLabel(category.getType(), category.getOwner()); panel.add(unit); @@ -775,7 +777,14 @@ void setNotificationShort( private void categorizeUnits( final Iterable categoryIter, final boolean damaged, final boolean disabled) { - for (final UnitCategory category : categoryIter) { + final List unitCategories = + StreamSupport.stream(categoryIter.spliterator(), false).collect(Collectors.toList()); + if (unitCategories.isEmpty()) { + return; + } + final GameData gameData = unitCategories.get(0).getUnitAttachment().getData(); + UnitSeparator.sortUnitCategories(unitCategories, gameData); + for (final UnitCategory category : unitCategories) { final JPanel panel = new JPanel(); final ImageIcon unitImage = uiContext diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleModel.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleModel.java index f76f8495ae..a16a286d4f 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleModel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BattleModel.java @@ -128,7 +128,8 @@ void refresh() { } unitPowerAndRollsMap = PowerStrengthAndRolls.build(units, combatValue); } - final Collection unitCategories = UnitSeparator.categorize(units); + final List unitCategories = + UnitSeparator.getSortedUnitCategories(units, gameData, uiContext.getMapData()); for (final UnitCategory category : unitCategories) { final int[] shift = new int[gameData.getDiceSides() + 1]; for (final Unit current : category.getUnits()) { diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/SimpleUnitPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/SimpleUnitPanel.java index 7114deedac..6b04cd1f84 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/SimpleUnitPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/SimpleUnitPanel.java @@ -1,5 +1,7 @@ package games.strategy.triplea.ui; +import com.google.common.collect.Lists; +import games.strategy.engine.data.GameData; import games.strategy.engine.data.GamePlayer; import games.strategy.engine.data.GameState; import games.strategy.engine.data.NamedAttachable; @@ -13,7 +15,9 @@ import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.image.UnitImageFactory; import games.strategy.triplea.util.UnitCategory; +import games.strategy.triplea.util.UnitSeparator; import java.awt.Image; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Set; @@ -120,7 +124,13 @@ public void setUnitsFromRepairRuleMap( */ public void setUnitsFromCategories(final Collection categories) { removeAll(); - for (final UnitCategory category : categories) { + if (categories.isEmpty()) { + return; + } + final GameData gameData = categories.iterator().next().getUnitAttachment().getData(); + final ArrayList unitCategories = Lists.newArrayList(categories); + UnitSeparator.sortUnitCategories(unitCategories, gameData); + for (final UnitCategory category : unitCategories) { addUnits( category.getOwner(), category.getUnits().size(), diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/UnitChooser.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/UnitChooser.java index a9576e9901..6788440436 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/UnitChooser.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/UnitChooser.java @@ -1,6 +1,7 @@ package games.strategy.triplea.ui; import com.google.common.annotations.VisibleForTesting; +import games.strategy.engine.data.GameData; import games.strategy.engine.data.Unit; import games.strategy.triplea.Properties; import games.strategy.triplea.ResourceLoader; @@ -23,6 +24,8 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -36,8 +39,10 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextArea; +import lombok.Getter; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; +import lombok.val; import org.triplea.java.Postconditions; import org.triplea.java.collections.IntegerMap; import org.triplea.swing.jpanel.GridBagConstraintsAnchor; @@ -297,7 +302,14 @@ private void layoutEntries() { 0)); autoSelectButton.addActionListener(e -> autoSelect()); int rowIndex = 1; - for (final ChooserEntry entry : entries) { + if (entries.isEmpty()) { + return; + } + final GameData gameData = entries.get(0).getCategory().getUnitAttachment().getData(); + entries.sort( + Comparator.comparing( + ChooserEntry::getCategory, UnitSeparator.getComparatorUnitCategories(gameData))); + for (val entry : Collections.unmodifiableList(entries)) { entry.createComponents(this, rowIndex); rowIndex++; } @@ -430,7 +442,7 @@ void disableMax() { */ @VisibleForTesting public final class ChooserEntry { - @VisibleForTesting public final UnitCategory category; + @Getter @VisibleForTesting public final UnitCategory category; private final List defaultHits; private final List hitTexts; private final List hitLabel = new ArrayList<>(); @@ -508,10 +520,6 @@ void set(final int value) { hitTexts.get(0).setValue(value); } - UnitCategory getCategory() { - return category; - } - void selectAll() { hitTexts.get(0).setValue(hitTexts.get(0).getMax()); } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java index f967155b7b..03d3be12f4 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java @@ -742,7 +742,9 @@ public void paint(final Graphics g) { } final var unitImageFactory = uiContext.getUnitImageFactory(); for (final Collection value : highlightedUnits) { - for (final UnitCategory category : UnitSeparator.categorize(value)) { + List unitCategories = + UnitSeparator.getSortedUnitCategories(value, gameData, mapData); + for (final UnitCategory category : unitCategories) { final @Nullable Rectangle r = tileManager.getUnitRect(category.getUnits(), gameData); if (r == null) { continue; @@ -896,7 +898,8 @@ public void setMouseShadowUnits(final @Nullable Collection units) { } } - final Set categories = UnitSeparator.categorize(units); + final List categories = + UnitSeparator.getSortedUnitCategories(units, gameData, uiContext.getMapData()); final int iconWidth = uiContext.getUnitImageFactory().getUnitImageWidth(); final int iconHeight = uiContext.getUnitImageFactory().getUnitImageHeight(); final int horizontalSpace = 5; diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/AvatarPanelFactory.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/AvatarPanelFactory.java index 6448d0a52d..d7d3d12442 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/AvatarPanelFactory.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/AvatarPanelFactory.java @@ -2,6 +2,7 @@ import com.google.common.base.Preconditions; import games.strategy.engine.data.GamePlayer; +import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.triplea.image.MapImage; @@ -9,13 +10,12 @@ import games.strategy.triplea.image.UnitImageFactory.ImageKey; import games.strategy.triplea.ui.panels.map.MapPanel; import games.strategy.triplea.ui.screen.UnitsDrawer; -import games.strategy.triplea.util.UnitCategory; +import games.strategy.triplea.util.UnitSeparator; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.image.BufferedImage; import java.util.Collection; -import java.util.Comparator; import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; @@ -74,14 +74,19 @@ class AvatarPanelFactory { * @param panelWidth How much horizontal space we have for drawing. * @return A panel containing a drawing of the unique images for each unit type. */ - JPanel buildPanel(final List units, final GamePlayer currentPlayer, final int panelWidth) { + JPanel buildPanel( + final List units, + final GamePlayer currentPlayer, + final Territory territory, + final int panelWidth) { final int renderingWidth = Math.min(panelWidth, MAX_RENDERING_WIDTH); final Icon unitIcon = units.isEmpty() ? new ImageIcon(createEmptyUnitStackImage(renderingWidth)) : new ImageIcon( - createUnitStackImage(unitImageFactory, currentPlayer, units, renderingWidth)); + createUnitStackImage( + unitImageFactory, currentPlayer, territory, units, renderingWidth)); return new JPanelBuilder() // .borderLayout() @@ -99,13 +104,13 @@ private Image createEmptyUnitStackImage(final int renderingWidth) { private static Image createUnitStackImage( final UnitImageFactory unitImageFactory, final GamePlayer player, + final Territory territory, final List units, final int renderingWidth) { Preconditions.checkArgument(!units.isEmpty()); - final var unitsToDraw = UnitScrollerModel.getUniqueUnitCategories(player, units); - + final var unitsToDraw = UnitSeparator.getSortedUnitCategories(units, territory, player); final var dimension = unitImageFactory.getImageDimensions( ImageKey.builder().type(unitsToDraw.get(0).getType()).player(player).build()); @@ -134,7 +139,6 @@ private static Image createUnitStackImage( "Draw location count (%s) should have matched units draw size (%s)", drawLocations.size(), unitsToDraw.size())); - unitsToDraw.sort(unitRenderingOrder(player)); for (int i = 0; i < drawLocations.size(); i++) { final var unitToDraw = unitsToDraw.get(i); final var imageToDraw = unitImageFactory.getImage(ImageKey.of(unitToDraw)); @@ -159,24 +163,6 @@ private static Image createUnitStackImage( return combinedImage; } - private static Comparator unitRenderingOrder(final GamePlayer currentPlayer) { - final Comparator isAir = - Comparator.comparing(unitCategory -> unitCategory.getUnitAttachment().getIsAir()); - final Comparator isSea = - Comparator.comparing(unitCategory -> unitCategory.getUnitAttachment().getIsSea()); - final Comparator unitAttackPower = - Comparator.comparingInt( - unitCategory -> unitCategory.getUnitAttachment().getAttack(currentPlayer)); - final Comparator unitName = - Comparator.comparing(unitCategory -> unitCategory.getType().getName()); - - return isAir // - .thenComparing(isSea) - .thenComparing(unitAttackPower) - .thenComparing(unitName) - .reversed(); - } - private static int countUnit(final UnitType unitType, final Collection units) { return units.stream() // .map(Unit::getType) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/UnitScroller.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/UnitScroller.java index b7818561bc..bed6caec4c 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/UnitScroller.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/unit/scroller/UnitScroller.java @@ -220,7 +220,7 @@ private void drawUnitAvatarPane(final Territory t) { selectUnitImagePanel.removeAll(); if (player != null) { selectUnitImagePanel.add( - avatarPanelFactory.buildPanel(moveableUnits, player, renderingWidth)); + avatarPanelFactory.buildPanel(moveableUnits, player, t, renderingWidth)); } SwingComponents.redraw(selectUnitImagePanel); }); diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/util/UnitSeparator.java b/game-app/game-core/src/main/java/games/strategy/triplea/util/UnitSeparator.java index 08ec45696f..13f6ad371e 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/util/UnitSeparator.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/util/UnitSeparator.java @@ -1,7 +1,7 @@ package games.strategy.triplea.util; +import games.strategy.engine.data.GameData; import games.strategy.engine.data.GamePlayer; -import games.strategy.engine.data.GameState; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nullable; @@ -48,36 +49,152 @@ public static class SeparatorCategories { } /** - * Finds unit categories, removes not displayed, and then sorts them into logical order to display - * based on: 1. Unit owner: territory owner, not at war with territory owner, player order in XML - * 2. Unit type: 0 movement, can't combat move, sea, air if sea territory, land, air if land - * territory 3. Within each of those groups sort the units by XML order in UnitList + * Finds unit categories from units of the Territory, removes not displayed ones + * according to MapData and then sorts them */ public static List getSortedUnitCategories( final Territory t, final MapData mapData) { - final GameState data = t.getData(); final List categories = new ArrayList<>(UnitSeparator.categorize(t.getUnits())); categories.removeIf(uc -> !mapData.shouldDrawUnit(uc.getType().getName())); - final List xmlUnitTypes = new ArrayList<>(data.getUnitTypeList().getAllUnitTypes()); - categories.sort( - Comparator.comparing( - UnitCategory::getOwner, - Comparator.comparing((final GamePlayer p) -> !p.equals(t.getOwner())) - .thenComparing(p -> Matches.isAtWar(p).test(t.getOwner())) - .thenComparing(data.getPlayerList().getPlayers()::indexOf)) - .thenComparing(uc -> Matches.unitTypeCanMove(uc.getOwner()).test(uc.getType())) - .thenComparing( - UnitCategory::getType, - Comparator.comparing( - (final UnitType ut) -> - !Matches.unitTypeCanNotMoveDuringCombatMove().test(ut)) - .thenComparing(ut -> !Matches.unitTypeIsSea().test(ut)) - .thenComparing(ut -> !(t.isWater() && Matches.unitTypeIsAir().test(ut))) - .thenComparing(ut -> !Matches.unitTypeIsLand().test(ut)) - .thenComparing(xmlUnitTypes::indexOf))); + categories.sort(getComparatorUnitCategories(t)); return categories; } + /** + * Finds unit categories from units of the Territory, removes not + * displayed ones according to MapData and then sorts them using also the + * GamePlayer + */ + public static List getSortedUnitCategories( + final Collection units, + final Territory t, + final MapData mapData, + final GamePlayer gamePlayer) { + final List categories = new ArrayList<>(UnitSeparator.categorize(units)); + categories.removeIf(uc -> !mapData.shouldDrawUnit(uc.getType().getName())); + categories.sort(getComparatorUnitCategories(t, gamePlayer)); + return categories; + } + + /** + * Finds unit categories from units of the Territory and then sorts them + * using also the GamePlayer + */ + public static List getSortedUnitCategories( + final Collection units, final Territory t, final GamePlayer gamePlayer) { + final List categories = new ArrayList<>(UnitSeparator.categorize(units)); + categories.sort(getComparatorUnitCategories(t, gamePlayer)); + return categories; + } + + /** + * Finds unit categories from units, removes not displayed ones according to + * MapData and then sorts them + */ + public static List getSortedUnitCategories( + final Collection units, final GameData gameData, final MapData mapData) { + final List categories = new ArrayList<>(UnitSeparator.categorize(units)); + categories.removeIf(uc -> !mapData.shouldDrawUnit(uc.getType().getName())); + categories.sort(getComparatorUnitCategories(gameData)); + return categories; + } + + /** Sorts a list of unit categories */ + public static void sortUnitCategories( + final List unitCategories, final GameData gameData) { + unitCategories.sort(getComparatorUnitCategories(gameData)); + } + + /** + * Sorts a list of UnitCategory with Territory and GamePlayer + * + */ + public static void sortUnitCategories( + final List unitCategories, final Territory t, final GamePlayer currentPlayer) { + unitCategories.sort(getComparatorUnitCategories(t, currentPlayer)); + } + + /** + * Returns Comparator for unit categories with current GameData Try to + * use a method returning List of UnitCategory> instead + */ + public static Comparator getComparatorUnitCategories(final GameData gameData) { + return getComparatorUnitCategories( + Optional.empty(), gameData, gameData.getHistory().getCurrentPlayer()); + } + + /** Returns Comparator for unit categories of a Territory */ + private static Comparator getComparatorUnitCategories( + final Territory t, final GamePlayer currentPlayer) { + final GameData gameData = t.getData(); + return getComparatorUnitCategories(Optional.of(t), gameData, currentPlayer); + } + + /** Returns Comparator for unit categories of a Territory */ + private static Comparator getComparatorUnitCategories(final Territory t) { + final GameData gameData = t.getData(); + return getComparatorUnitCategories( + Optional.of(t), gameData, gameData.getHistory().getCurrentPlayer()); + } + + /** Returns Comparator for unit categories of a Territory */ + private static Comparator getComparatorUnitCategories( + final Optional optionalTerritory, + final GameData gameData, + final GamePlayer currentPlayer) { + final List xmlUnitTypes = + new ArrayList<>(gameData.getUnitTypeList().getAllUnitTypes()); + final List players = gameData.getPlayerList().getPlayers(); + return getComparatorUnitCategories(optionalTerritory, currentPlayer, players, xmlUnitTypes); + } + + /** + * Returns Comparator for unit categories to allow sorting into logical order to + * display based on: 1. Unit owner: territory owner, not at war with territory owner, player order + * in XML 2. Unit type: 0 movement, can't combat move, sea, air if sea territory, land, air if + * land territory 3. Within each of those groups sort the units by XML order in UnitList + */ + private static Comparator getComparatorUnitCategories( + final Optional optionalTerritory, + final GamePlayer currentPlayer, + final List players, + final List xmlUnitTypes) { + return Comparator.comparing( + UnitCategory::getOwner, // 1. Unit owner + Comparator.comparing( + (final GamePlayer p) -> + !(optionalTerritory.isPresent() + && p.equals(optionalTerritory.get().getOwner()))) + .thenComparing( + p -> + (optionalTerritory.isPresent() + && Matches.isAtWar(p).test(optionalTerritory.get().getOwner()))) + .thenComparing(players::indexOf)) + .thenComparing( + uc -> Matches.unitTypeCanMove(uc.getOwner()).test(uc.getType())) // 2. Unit type + .thenComparing( + UnitCategory::getType, + Comparator.comparing( + (final UnitType ut) -> !Matches.unitTypeCanNotMoveDuringCombatMove().test(ut)) + .thenComparing(ut -> !Matches.unitTypeIsSea().test(ut)) + .thenComparing( + ut -> + !(optionalTerritory.isPresent() + && optionalTerritory.get().isWater() + && Matches.unitTypeIsAir().test(ut))) + .thenComparing(ut -> !Matches.unitTypeIsLand().test(ut))) + .thenComparingInt(ut -> ut.getUnitAttachment().getMaxBuiltPerPlayer()) + .thenComparing( + uc -> + uc.getUnitAttachment() + .getAttack( + (currentPlayer == null + ? uc.getOwner() + : currentPlayer))) // should be currentPlayer + .thenComparing( + UnitCategory::getType, Comparator.comparing(xmlUnitTypes::indexOf)); // 3. Final sorting + } + public static Set categorize(final Collection units) { return categorize(units, SeparatorCategories.builder().build()); } diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/AbstractUndoableMovesPanel.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/AbstractUndoableMovesPanel.java index 9700507341..d4683540af 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/AbstractUndoableMovesPanel.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/AbstractUndoableMovesPanel.java @@ -118,8 +118,10 @@ public void paint(final Graphics g) { private JComponent newComponentForMove(final AbstractUndoableMove move) { final Box unitsBox = new Box(BoxLayout.X_AXIS); unitsBox.add(new JLabel((move.getIndex() + 1) + ") ")); - final Collection unitCategories = UnitSeparator.categorize(move.getUnits()); final Dimension buttonSize = new Dimension(80, 22); + final List unitCategories = + UnitSeparator.getSortedUnitCategories( + move.getUnits(), movePanel.getData(), movePanel.getMap().getUiContext().getMapData()); for (final UnitCategory category : unitCategories) { final ImageIcon icon = movePanel.getMap().getUiContext().getUnitImageFactory().getIcon(ImageKey.of(category));