GUI: hand - added default card sorting on mulligan (lands > other > creatures; mana value, name);

This commit is contained in:
Oleg Agafonov 2023-12-14 22:13:07 +04:00
parent 525ca9a1a2
commit 10641f8d63
10 changed files with 188 additions and 35 deletions

View file

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

View file

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

View file

@ -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<UUID, PlayAreaPanel> players = new HashMap<>();
private final Map<UUID, Boolean> playersWhoLeft = new HashMap<>();
private final Map<UUID, PlayAreaPanel> players = new LinkedHashMap<>();
private final Map<UUID, Boolean> playersWhoLeft = new LinkedHashMap<>();
// non modal frames
private final Map<UUID, CardInfoWindowDialog> 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<String, Card> loadedCards = new HashMap<>();
private final Map<String, Card> loadedCards = new HashMap<>();
private int storedHeight;
private Map<String, HoverButton> 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;

View file

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

View file

@ -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<String> goodList) {
MulliganDefaultHandSorter sorter = new MulliganDefaultHandSorter();
// prepare good list
List<Card> 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<Card> bad = new ArrayList<>(good);
Collections.shuffle(bad);
CardsImpl sorted = new CardsImpl(bad);
sorted.sortCards(currentGame, sorter);
List<Card> badSorted = new ArrayList<>(sorted.getCards(currentGame));
assertList(good, badSorted);
});
}
private void assertList(List<Card> need, List<Card> 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
));
}
}

View file

@ -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<UUID>, Serializable, Copyable<Cards> {
@ -47,7 +44,7 @@ public interface Cards extends Set<UUID>, Serializable, Copyable<Cards> {
Set<Card> 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<UUID>, Serializable, Copyable<Cards> {
* @param game The ongoing game.
*/
void removeZone(Zone zone, Game game);
void sortCards(Game game, Comparator<? super Card> comparator);
}

View file

@ -209,4 +209,20 @@ public class CardsImpl extends LinkedHashSet<UUID> implements Cards, Serializabl
public void removeZone(Zone zone, Game game) {
removeIf(uuid -> game.getState().getZone(uuid) == zone);
}
@Override
public void sortCards(Game game, Comparator<? super Card> comparator) {
// workaround to sort linked list - re-create it, it must be safe for game
List<Card> 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()));
}
}

View file

@ -12,7 +12,7 @@ public abstract class Mulligan implements Serializable {
protected final int freeMulligans;
protected final Map<UUID, Integer> 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());
}
}

View file

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

View file

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