From 10641f8d6333bc6307b9850298c6ed62591a8494 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 14 Dec 2023 22:13:07 +0400 Subject: [PATCH] GUI: hand - added default card sorting on mulligan (lands > other > creatures; mana value, name); --- .../main/java/mage/client/cards/Cards.java | 4 - .../client/dialog/CardInfoWindowDialog.java | 17 +-- .../main/java/mage/client/game/GamePanel.java | 20 ++-- .../main/java/mage/client/game/HandPanel.java | 2 +- .../test/mulligan/MulliganCardSorterTest.java | 113 ++++++++++++++++++ Mage/src/main/java/mage/cards/Cards.java | 9 +- Mage/src/main/java/mage/cards/CardsImpl.java | 16 +++ .../java/mage/game/mulligan/Mulligan.java | 5 +- .../mulligan/MulliganDefaultHandSorter.java | 34 ++++++ .../game/mulligan/SmoothedLondonMulligan.java | 3 + 10 files changed, 188 insertions(+), 35 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganCardSorterTest.java create mode 100644 Mage/src/main/java/mage/game/mulligan/MulliganDefaultHandSorter.java diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index d0c8395ac31..e9e5e76a6ab 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -127,10 +127,6 @@ } } - public boolean loadCards(SimpleCardsView cardsView, BigCard bigCard, UUID gameId) { - return loadCards(CardsViewUtil.convertSimple(cardsView), bigCard, gameId, true); - } - public boolean loadCards(CardsView cardsView, BigCard bigCard, UUID gameId, boolean revertOrder) { boolean changed = false; diff --git a/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java b/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java index e5427247754..ab6f4fd1d1a 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java @@ -1,14 +1,11 @@ package mage.client.dialog; import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.beans.PropertyVetoException; import java.util.*; import javax.swing.*; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; -import javax.swing.plaf.basic.BasicInternalFrameUI; import mage.cards.MageCard; import mage.client.cards.BigCard; @@ -117,7 +114,7 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi cards.changeGUISize(); } - public void loadCards(ExileView exile, BigCard bigCard, UUID gameId) { + public void loadCardsAndShow(ExileView exile, BigCard bigCard, UUID gameId) { boolean changed = cards.loadCards(exile, bigCard, gameId, true); String titel = name + " (" + exile.size() + ')'; setTitle(titel); @@ -136,16 +133,8 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi } } - public void loadCards(SimpleCardsView showCards, BigCard bigCard, UUID gameId) { - cards.loadCards(showCards, bigCard, gameId); - showAndPositionWindow(); - } - - public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId) { - loadCards(showCards, bigCard, gameId, true); - } - - public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId, boolean revertOrder) { + // TODO: remove oudated code with revertOrder (wait new release and delete if no bug reports for diff windows with cards, 2023-12-14) + public void loadCardsAndShow(CardsView showCards, BigCard bigCard, UUID gameId, boolean revertOrder) { cards.loadCards(showCards, bigCard, gameId, revertOrder); // additional info for grave windows diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 92769bcfe7f..c7076f94828 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -72,8 +72,8 @@ public final class GamePanel extends javax.swing.JPanel { private static final String CMD_AUTO_ORDER_NAME_LAST = "cmdAutoOrderNameLast"; private static final String CMD_AUTO_ORDER_RESET_ALL = "cmdAutoOrderResetAll"; - private final Map players = new HashMap<>(); - private final Map playersWhoLeft = new HashMap<>(); + private final Map players = new LinkedHashMap<>(); + private final Map playersWhoLeft = new LinkedHashMap<>(); // non modal frames private final Map exiles = new HashMap<>(); @@ -103,7 +103,7 @@ public final class GamePanel extends javax.swing.JPanel { private boolean menuNameSet = false; private boolean handCardsOfOpponentAvailable = false; - private Map loadedCards = new HashMap<>(); + private final Map loadedCards = new HashMap<>(); private int storedHeight; private Map hoverButtons; @@ -893,7 +893,7 @@ public final class GamePanel extends javax.swing.JPanel { if (windowDialog.isClosed()) { graveyardWindows.remove(player.getName()); } else { - windowDialog.loadCards(player.getGraveyard(), bigCard, gameId, false); + windowDialog.loadCardsAndShow(player.getGraveyard(), bigCard, gameId, false); } } @@ -904,7 +904,7 @@ public final class GamePanel extends javax.swing.JPanel { if (windowDialog.isClosed()) { sideboardWindows.remove(player.getName()); } else { - windowDialog.loadCards(player.getSideboard(), bigCard, gameId, false); + windowDialog.loadCardsAndShow(player.getSideboard(), bigCard, gameId, false); } } @@ -959,7 +959,7 @@ public final class GamePanel extends javax.swing.JPanel { MageFrame.getDesktop().add(exileWindow, JLayeredPane.PALETTE_LAYER); exileWindow.show(); } - exileWindow.loadCards(exile, bigCard, gameId); + exileWindow.loadCardsAndShow(exile, bigCard, gameId); } // update open or remove closed card hints windows @@ -1334,7 +1334,7 @@ public final class GamePanel extends javax.swing.JPanel { graveyardWindows.put(playerName, newGraveyard); MageFrame.getDesktop().add(newGraveyard, JLayeredPane.PALETTE_LAYER); // use graveyards to sync selection (don't use player data here) - newGraveyard.loadCards(graveyards.get(playerName), bigCard, gameId, false); + newGraveyard.loadCardsAndShow(graveyards.get(playerName), bigCard, gameId, false); } private void clearClosedCardHintsWindows() { @@ -1387,7 +1387,7 @@ public final class GamePanel extends javax.swing.JPanel { sideboardWindows.put(playerView.getName(), windowDialog); MageFrame.getDesktop().add(windowDialog, JLayeredPane.PALETTE_LAYER); // use sideboards to sync selection (don't use player data here) - windowDialog.loadCards(sideboards.get(playerView.getName()), bigCard, gameId, false); + windowDialog.loadCardsAndShow(sideboards.get(playerView.getName()), bigCard, gameId, false); } public void openTopLibraryWindow(String playerName) { @@ -1448,10 +1448,10 @@ public final class GamePanel extends javax.swing.JPanel { case REVEAL: case REVEAL_TOP_LIBRARY: case COMPANION: - cardInfoWindowDialog.loadCards((CardsView) cardsView, bigCard, gameId); + cardInfoWindowDialog.loadCardsAndShow((CardsView) cardsView, bigCard, gameId, false); break; case LOOKED_AT: - cardInfoWindowDialog.loadCards((SimpleCardsView) cardsView, bigCard, gameId); + cardInfoWindowDialog.loadCardsAndShow(CardsViewUtil.convertSimple((SimpleCardsView) cardsView), bigCard, gameId, false); break; default: break; diff --git a/Mage.Client/src/main/java/mage/client/game/HandPanel.java b/Mage.Client/src/main/java/mage/client/game/HandPanel.java index ce7720e2e02..c7e0543c105 100644 --- a/Mage.Client/src/main/java/mage/client/game/HandPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/HandPanel.java @@ -66,7 +66,7 @@ public class HandPanel extends JPanel { } public void loadCards(CardsView cards, BigCard bigCard, UUID gameId) { - hand.loadCards(cards, bigCard, gameId, true); + hand.loadCards(cards, bigCard, gameId, false); } private JPanel jPanel; diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganCardSorterTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganCardSorterTest.java new file mode 100644 index 00000000000..7f20b227d4a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganCardSorterTest.java @@ -0,0 +1,113 @@ +package org.mage.test.mulligan; + +import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.mulligan.MulliganDefaultHandSorter; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * @author JayDi85 + */ +public class MulliganCardSorterTest extends CardTestPlayerBase { + + private void assertHandSort(List goodList) { + MulliganDefaultHandSorter sorter = new MulliganDefaultHandSorter(); + + // prepare good list + List good = goodList + .stream() + .map(name -> currentGame.getCards() + .stream() + .filter(c -> c.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Can't find testing card " + name)) + ) + .collect(Collectors.toList()); + + // multiple tests with diff starting order + IntStream.range(0, 5).forEach(x -> { + List bad = new ArrayList<>(good); + Collections.shuffle(bad); + + CardsImpl sorted = new CardsImpl(bad); + sorted.sortCards(currentGame, sorter); + List badSorted = new ArrayList<>(sorted.getCards(currentGame)); + assertList(good, badSorted); + }); + } + + private void assertList(List need, List sorted) { + String needStr = need.stream().map(Card::getName).collect(Collectors.joining("; ")); + String sortedStr = sorted.stream().map(Card::getName).collect(Collectors.joining("; ")); + Assert.assertEquals("bad sorting", needStr, sortedStr); + } + + @Test + public void test_HandSorting() { + // init cards for sort testing + // lands + addCard(Zone.HAND, playerA, "Forest", 1); + addCard(Zone.HAND, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Island", 1); + // other + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); // mana 1, instant + addCard(Zone.HAND, playerA, "From Beyond", 1); // mana 4, enchantment + addCard(Zone.HAND, playerA, "Samite Blessing", 1); // mana 1, enchantment + addCard(Zone.HAND, playerA, "Druid's Call", 1); // mana 2, enchantment + // creatures + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // mana 2 + addCard(Zone.HAND, playerA, "Aspiring Champion", 1); // mana 4 + addCard(Zone.HAND, playerA, "Yellow Scarves Troops", 1); // mana 2 + addCard(Zone.HAND, playerA, "Marchesa's Infiltrator", 1); // mana 3 + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // lands by name + assertHandSort(Arrays.asList( + "Forest", + "Island", + "Mountain" + )); + + // creatures by mana + assertHandSort(Arrays.asList( + "Grizzly Bears", // 2 + "Yellow Scarves Troops", // 2 + "Marchesa's Infiltrator", // 3 + "Aspiring Champion" // 4 + )); + + // other by mana + assertHandSort(Arrays.asList( + "Lightning Bolt", // 1 + "Samite Blessing", // 1 + "Druid's Call", // 2 + "From Beyond" // 4 + )); + + // lands > others > creatures + assertHandSort(Arrays.asList( + "Forest", // land + "Island", // land + "Lightning Bolt", // other, 1 + "Samite Blessing", // other, 1 + "Druid's Call", // other, 2 + "Grizzly Bears", // creature, 2 + "Yellow Scarves Troops" // creature, 2 + )); + + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/cards/Cards.java b/Mage/src/main/java/mage/cards/Cards.java index 2dde930b91e..710921890a4 100644 --- a/Mage/src/main/java/mage/cards/Cards.java +++ b/Mage/src/main/java/mage/cards/Cards.java @@ -7,10 +7,7 @@ import mage.game.Game; import mage.util.Copyable; import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; public interface Cards extends Set, Serializable, Copyable { @@ -47,7 +44,7 @@ public interface Cards extends Set, Serializable, Copyable { Set getCards(FilterCard filter, UUID playerId, Ability source, Game game); - String getValue(Game game); + String getValue(Game game); // AI related code to find changes in game state /** * Get a collection view of the unique non-null cards in this set. @@ -82,4 +79,6 @@ public interface Cards extends Set, Serializable, Copyable { * @param game The ongoing game. */ void removeZone(Zone zone, Game game); + + void sortCards(Game game, Comparator comparator); } diff --git a/Mage/src/main/java/mage/cards/CardsImpl.java b/Mage/src/main/java/mage/cards/CardsImpl.java index 977a042aeee..9970002b3c3 100644 --- a/Mage/src/main/java/mage/cards/CardsImpl.java +++ b/Mage/src/main/java/mage/cards/CardsImpl.java @@ -209,4 +209,20 @@ public class CardsImpl extends LinkedHashSet implements Cards, Serializabl public void removeZone(Zone zone, Game game) { removeIf(uuid -> game.getState().getZone(uuid) == zone); } + + @Override + public void sortCards(Game game, Comparator comparator) { + // workaround to sort linked list - re-create it, it must be safe for game + List newList = this + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .sorted(comparator) + .collect(Collectors.toList()); + if (newList.size() != this.size()) { + throw new IllegalStateException("Wrong code usage: found unknown card id in hand while sorting, game is broken"); + } + this.clear(); + this.addAll(newList.stream().map(Card::getId).collect(Collectors.toList())); + } } diff --git a/Mage/src/main/java/mage/game/mulligan/Mulligan.java b/Mage/src/main/java/mage/game/mulligan/Mulligan.java index eb65030d5f7..8307b0ea6e1 100644 --- a/Mage/src/main/java/mage/game/mulligan/Mulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/Mulligan.java @@ -12,7 +12,7 @@ public abstract class Mulligan implements Serializable { protected final int freeMulligans; protected final Map usedFreeMulligans = new HashMap<>(); - public Mulligan(int freeMulligans) { + Mulligan(int freeMulligans) { this.freeMulligans = freeMulligans; } @@ -93,5 +93,8 @@ public abstract class Mulligan implements Serializable { public void drawHand(int numCards, Player player, Game game){ player.drawCards(numCards, null, game); + + // default hand sorting + player.getHand().sortCards(game, new MulliganDefaultHandSorter()); } } diff --git a/Mage/src/main/java/mage/game/mulligan/MulliganDefaultHandSorter.java b/Mage/src/main/java/mage/game/mulligan/MulliganDefaultHandSorter.java new file mode 100644 index 00000000000..7a4e37d09c8 --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/MulliganDefaultHandSorter.java @@ -0,0 +1,34 @@ +package mage.game.mulligan; + +import mage.cards.Card; + +import java.util.Comparator; + +/** + * GUI related, hand card sorting for mulligan + * + * @author JayDi85 + */ +public class MulliganDefaultHandSorter implements Comparator { + + @Override + public int compare(Card c1, Card c2) { + // groups: lands > other > creatures + // inside group: by mana value, by name + + // lands + if (c1.isLand() != c2.isLand()) { + return Boolean.compare(c2.isLand(), c1.isLand()); + } + // creatures + if (c1.isCreature() != c2.isCreature()) { + return Boolean.compare(c1.isCreature(), c2.isCreature()); + } + // by mana + if (c1.getManaValue() != c2.getManaValue()) { + return Integer.compare(c1.getManaValue(), c2.getManaValue()); + } + // by name + return c1.getName().compareTo(c2.getName()); + } +} diff --git a/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java index 3fedd02d7ab..8fd9cf2283c 100644 --- a/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java @@ -66,6 +66,9 @@ public class SmoothedLondonMulligan extends LondonMulligan { } else { //not enough cards in library or hand, just do a normal draw instead player.drawCards(numCards, null, game); } + + // default hand sorting + player.getHand().sortCards(game, new MulliganDefaultHandSorter()); } @Override