mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
deck improves:
* gui: removed public deck hash info; * gui: improved xmage dck-file - now it correctly load a card's amount (related to files from third party services); * server: fixed wrong cheating warning on deck construction (closes #11877); * refactor: removed outdated hash code and calculations; * other: added docs, added multiple deck hash tests;
This commit is contained in:
parent
889c1125e8
commit
7817a5cac6
32 changed files with 551 additions and 247 deletions
|
|
@ -1,28 +1,34 @@
|
|||
package mage.cards.decks;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.game.GameException;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.DeckUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Server side deck with workable cards, also can be used in GUI
|
||||
* <p>
|
||||
* If you need text only deck then look at DeckCardLists
|
||||
*/
|
||||
public class Deck implements Serializable, Copyable<Deck> {
|
||||
|
||||
static final int MAX_CARDS_PER_DECK = 2000;
|
||||
|
||||
private String name;
|
||||
private String name; // TODO: must rework somehow - tiny leaders use deck name to find commander card and hide it for a user
|
||||
private final Set<Card> cards = new LinkedHashSet<>();
|
||||
private final Set<Card> sideboard = new LinkedHashSet<>();
|
||||
private DeckCardLayout cardsLayout;
|
||||
private DeckCardLayout sideboardLayout;
|
||||
private long deckHashCode = 0;
|
||||
private long deckCompleteHashCode = 0;
|
||||
private DeckCardLayout cardsLayout; // client side only
|
||||
private DeckCardLayout sideboardLayout; // client side only
|
||||
|
||||
public Deck() {
|
||||
super();
|
||||
|
|
@ -34,8 +40,6 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
this.sideboard.addAll(deck.sideboard.stream().map(Card::copy).collect(Collectors.toList()));
|
||||
this.cardsLayout = deck.cardsLayout == null ? null : deck.cardsLayout.copy();
|
||||
this.sideboardLayout = deck.sideboardLayout == null ? null : deck.sideboardLayout.copy();
|
||||
this.deckHashCode = deck.deckHashCode;
|
||||
this.deckCompleteHashCode = deck.deckCompleteHashCode;
|
||||
}
|
||||
|
||||
public static Deck load(DeckCardLists deckCardLists) throws GameException {
|
||||
|
|
@ -46,27 +50,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
return Deck.load(deckCardLists, ignoreErrors, true);
|
||||
}
|
||||
|
||||
public static Deck append(Deck deckToAppend, Deck currentDeck) throws GameException {
|
||||
List<String> deckCardNames = new ArrayList<>();
|
||||
|
||||
for (Card card : deckToAppend.getCards()) {
|
||||
if (card != null) {
|
||||
currentDeck.cards.add(card);
|
||||
deckCardNames.add(card.getName());
|
||||
}
|
||||
}
|
||||
List<String> sbCardNames = new ArrayList<>();
|
||||
for (Card card : deckToAppend.getSideboard()) {
|
||||
if (card != null) {
|
||||
currentDeck.sideboard.add(card);
|
||||
deckCardNames.add(card.getName());
|
||||
}
|
||||
}
|
||||
Collections.sort(deckCardNames);
|
||||
Collections.sort(sbCardNames);
|
||||
String deckString = deckCardNames.toString() + sbCardNames.toString();
|
||||
currentDeck.setDeckHashCode(DeckUtil.fixedHash(deckString));
|
||||
return currentDeck;
|
||||
public static Deck append(Deck sourceDeck, Deck currentDeck) throws GameException {
|
||||
Deck newDeck = currentDeck.copy();
|
||||
sourceDeck.getCards().forEach(card -> {
|
||||
newDeck.cards.add(card.copy());
|
||||
});
|
||||
sourceDeck.getSideboard().forEach(card -> {
|
||||
newDeck.sideboard.add(card.copy());
|
||||
});
|
||||
return newDeck;
|
||||
}
|
||||
|
||||
public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException {
|
||||
|
|
@ -87,49 +79,45 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
deck.setName(deckCardLists.getName());
|
||||
deck.cardsLayout = deckCardLists.getCardLayout() == null ? null : deckCardLists.getCardLayout().copy();
|
||||
deck.sideboardLayout = deckCardLists.getSideboardLayout() == null ? null : deckCardLists.getSideboardLayout().copy();
|
||||
List<String> deckCardNames = new ArrayList<>();
|
||||
int totalCards = 0;
|
||||
for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) {
|
||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||
if (card != null) {
|
||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||
break;
|
||||
}
|
||||
deck.cards.add(card);
|
||||
deckCardNames.add(card.getName());
|
||||
totalCards++;
|
||||
|
||||
} else if (!ignoreErrors) {
|
||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||
}
|
||||
}
|
||||
List<String> sbCardNames = new ArrayList<>();
|
||||
for (DeckCardInfo deckCardInfo : deckCardLists.getSideboard()) {
|
||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||
if (card != null) {
|
||||
// load only real cards
|
||||
int totalCards = 0;
|
||||
|
||||
// main
|
||||
main:
|
||||
for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) {
|
||||
for (int i = 1; i <= deckCardInfo.getAmount(); i++) {
|
||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||
break;
|
||||
break main;
|
||||
}
|
||||
|
||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||
if (card != null) {
|
||||
deck.cards.add(card);
|
||||
totalCards++;
|
||||
} else if (!ignoreErrors) {
|
||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||
}
|
||||
deck.sideboard.add(card);
|
||||
sbCardNames.add(card.getName());
|
||||
totalCards++;
|
||||
} else if (!ignoreErrors) {
|
||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||
}
|
||||
}
|
||||
Collections.sort(deckCardNames);
|
||||
Collections.sort(sbCardNames);
|
||||
String deckString = deckCardNames.toString() + sbCardNames.toString();
|
||||
deck.setDeckHashCode(DeckUtil.fixedHash(deckString));
|
||||
if (sbCardNames.isEmpty()) {
|
||||
deck.setDeckCompleteHashCode(deck.getDeckHashCode());
|
||||
} else {
|
||||
List<String> deckAllCardNames = new ArrayList<>();
|
||||
deckAllCardNames.addAll(deckCardNames);
|
||||
deckAllCardNames.addAll(sbCardNames);
|
||||
Collections.sort(deckAllCardNames);
|
||||
deck.setDeckCompleteHashCode(DeckUtil.fixedHash(deckAllCardNames.toString()));
|
||||
|
||||
// sideboard
|
||||
side:
|
||||
for (DeckCardInfo deckCardInfo : deckCardLists.getSideboard()) {
|
||||
for (int i = 1; i <= deckCardInfo.getAmount(); i++) {
|
||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||
break side;
|
||||
}
|
||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||
if (card != null) {
|
||||
deck.sideboard.add(card);
|
||||
totalCards++;
|
||||
} else if (!ignoreErrors) {
|
||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deck;
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +137,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
String cardError = String.format("Card not found - %s - %s - %s in deck %s.",
|
||||
deckCardInfo.getCardName(),
|
||||
deckCardInfo.getSetCode(),
|
||||
deckCardInfo.getCardNum(),
|
||||
deckCardInfo.getCardNumber(),
|
||||
deckName
|
||||
);
|
||||
cardError += "\n\nPossible reasons:";
|
||||
|
|
@ -168,15 +156,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
CardInfo cardInfo;
|
||||
if (cardInfoCache != null) {
|
||||
// from cache
|
||||
String key = String.format("%s_%s", deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
||||
String key = String.format("%s_%s", deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||
cardInfo = cardInfoCache.getOrDefault(key, null);
|
||||
if (cardInfo == null) {
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||
cardInfoCache.put(key, cardInfo);
|
||||
}
|
||||
} else {
|
||||
// from db
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||
}
|
||||
|
||||
if (cardInfo == null) {
|
||||
|
|
@ -190,13 +178,18 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
}
|
||||
}
|
||||
|
||||
public DeckCardLists getDeckCardLists() {
|
||||
/**
|
||||
* Prepare new deck with cards only without layout and other client side settings.
|
||||
* Use it for any export and server's deck update/submit
|
||||
*/
|
||||
public DeckCardLists prepareCardsOnlyDeck() {
|
||||
DeckCardLists deckCardLists = new DeckCardLists();
|
||||
|
||||
deckCardLists.setName(name);
|
||||
|
||||
for (Card card : cards) {
|
||||
deckCardLists.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||
}
|
||||
|
||||
for (Card card : sideboard) {
|
||||
deckCardLists.getSideboard().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||
}
|
||||
|
|
@ -227,6 +220,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
return cards;
|
||||
}
|
||||
|
||||
// TODO: delete and replace by getCards()
|
||||
public Set<Card> getMaindeckCards() {
|
||||
return cards
|
||||
.stream()
|
||||
|
|
@ -262,22 +256,6 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
return sideboardLayout;
|
||||
}
|
||||
|
||||
public long getDeckHashCode() {
|
||||
return deckHashCode;
|
||||
}
|
||||
|
||||
public void setDeckHashCode(long deckHashCode) {
|
||||
this.deckHashCode = deckHashCode;
|
||||
}
|
||||
|
||||
public long getDeckCompleteHashCode() {
|
||||
return deckCompleteHashCode;
|
||||
}
|
||||
|
||||
public void setDeckCompleteHashCode(long deckHashCode) {
|
||||
this.deckCompleteHashCode = deckHashCode;
|
||||
}
|
||||
|
||||
public void clearLayouts() {
|
||||
this.cardsLayout = null;
|
||||
this.sideboardLayout = null;
|
||||
|
|
@ -287,4 +265,11 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
public Deck copy() {
|
||||
return new Deck(this);
|
||||
}
|
||||
|
||||
public long getDeckHash() {
|
||||
return DeckUtil.getDeckHash(
|
||||
this.cards.stream().map(MageObject::getName).collect(Collectors.toList()),
|
||||
this.sideboard.stream().map(MageObject::getName).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.cards.decks;
|
||||
|
||||
import mage.util.Copyable;
|
||||
|
|
@ -7,14 +5,18 @@ import mage.util.Copyable;
|
|||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Client side: card record in deck file
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
||||
|
||||
static final int MAX_AMOUNT_PER_CARD = 99;
|
||||
|
||||
private String cardName;
|
||||
private String setCode;
|
||||
private String cardNum;
|
||||
private int quantity;
|
||||
private String cardNumber;
|
||||
private int amount;
|
||||
|
||||
public DeckCardInfo() {
|
||||
super();
|
||||
|
|
@ -23,19 +25,29 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
|||
protected DeckCardInfo(final DeckCardInfo info) {
|
||||
this.cardName = info.cardName;
|
||||
this.setCode = info.setCode;
|
||||
this.cardNum = info.cardNum;
|
||||
this.quantity = info.quantity;
|
||||
this.cardNumber = info.cardNumber;
|
||||
this.amount = info.amount;
|
||||
}
|
||||
|
||||
public DeckCardInfo(String cardName, String cardNum, String setCode) {
|
||||
this(cardName, cardNum, setCode, 1);
|
||||
public DeckCardInfo(String cardName, String cardNumber, String setCode) {
|
||||
this(cardName, cardNumber, setCode, 1);
|
||||
}
|
||||
|
||||
public DeckCardInfo(String cardName, String cardNum, String setCode, int quantity) {
|
||||
public static void makeSureCardAmountFine(int amount, String cardName) {
|
||||
// runtime check
|
||||
if (amount > MAX_AMOUNT_PER_CARD) {
|
||||
// xmage uses 1 for amount all around, but keep that protection anyway
|
||||
throw new IllegalArgumentException("Found too big amount for a deck's card: " + cardName + " - " + amount);
|
||||
}
|
||||
}
|
||||
|
||||
public DeckCardInfo(String cardName, String cardNumber, String setCode, int amount) {
|
||||
makeSureCardAmountFine(amount, cardName);
|
||||
|
||||
this.cardName = cardName;
|
||||
this.cardNum = cardNum;
|
||||
this.cardNumber = cardNumber;
|
||||
this.setCode = setCode;
|
||||
this.quantity = quantity;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getCardName() {
|
||||
|
|
@ -46,21 +58,16 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
|||
return setCode;
|
||||
}
|
||||
|
||||
public String getCardNum() {
|
||||
return cardNum;
|
||||
public String getCardNumber() {
|
||||
return cardNumber;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public DeckCardInfo increaseQuantity() {
|
||||
quantity++;
|
||||
return this;
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getCardKey() {
|
||||
return setCode + cardNum;
|
||||
return setCode + cardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import mage.util.Copyable;
|
|||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Client side deck with text only.
|
||||
* <p>
|
||||
* Can contain restricted, un-implemented or unknown cards
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
|
|
@ -46,12 +48,15 @@ public class DeckCardLists implements Serializable, Copyable<DeckCardLists> {
|
|||
public DeckCardLayout getCardLayout() {
|
||||
return cardLayout;
|
||||
}
|
||||
|
||||
public void setCardLayout(DeckCardLayout layout) {
|
||||
this.cardLayout = layout;
|
||||
}
|
||||
|
||||
public DeckCardLayout getSideboardLayout() {
|
||||
return sideboardLayout;
|
||||
}
|
||||
|
||||
public void setSideboardLayout(DeckCardLayout layout) {
|
||||
this.sideboardLayout = layout;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ public enum DeckFormats {
|
|||
return res;
|
||||
}
|
||||
|
||||
// TODO: need refactor and remove all that methods to 1-3 versions only
|
||||
|
||||
public static void writeDeck(String file, DeckCardLists deck) throws IOException {
|
||||
writeDeck(new File(file), deck);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ public enum ExpansionRepository {
|
|||
instanceInitialized = true;
|
||||
|
||||
eventSource.fireRepositoryDbLoaded();
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
// TODO: add app close?
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import mage.cards.repository.CardRepository;
|
|||
import mage.constants.Zone;
|
||||
import mage.game.command.Emblem;
|
||||
import mage.util.CardUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -50,7 +49,7 @@ public final class EmblemOfCard extends Emblem {
|
|||
public static Card cardFromDeckInfo(DeckCardInfo info) {
|
||||
return lookupCard(
|
||||
info.getCardName(),
|
||||
info.getCardNum(),
|
||||
info.getCardNumber(),
|
||||
info.getSetCode(),
|
||||
"DeckCardInfo"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public interface Match {
|
|||
|
||||
void submitDeck(UUID playerId, Deck deck);
|
||||
|
||||
boolean updateDeck(UUID playerId, Deck deck);
|
||||
void updateDeck(UUID playerId, Deck deck);
|
||||
|
||||
void startMatch();
|
||||
|
||||
|
|
|
|||
|
|
@ -379,9 +379,8 @@ public abstract class MatchImpl implements Match {
|
|||
public void submitDeck(UUID playerId, Deck deck) {
|
||||
MatchPlayer player = getPlayer(playerId);
|
||||
if (player != null) {
|
||||
// make sure the deck name (needed for Tiny Leaders) won't get lost by sideboarding
|
||||
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||
deck.setName(player.getDeck().getName());
|
||||
deck.setDeckHashCode(player.getDeck().getDeckHashCode());
|
||||
player.submitDeck(deck);
|
||||
}
|
||||
synchronized (this) {
|
||||
|
|
@ -390,20 +389,14 @@ public abstract class MatchImpl implements Match {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
boolean validDeck = true;
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
// used for auto-save deck
|
||||
MatchPlayer player = getPlayer(playerId);
|
||||
if (player != null) {
|
||||
// Check if the cards included in the deck are the same as in the original deck
|
||||
validDeck = (player.getDeck().getDeckCompleteHashCode() == deck.getDeckCompleteHashCode());
|
||||
if (validDeck == false) {
|
||||
// clear the deck so the player cheating looses the game
|
||||
deck.getCards().clear();
|
||||
deck.getSideboard().clear();
|
||||
}
|
||||
player.updateDeck(deck);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
return validDeck;
|
||||
|
||||
player.updateDeck(deck);
|
||||
}
|
||||
|
||||
protected String createGameStartMessage() {
|
||||
|
|
@ -416,9 +409,6 @@ public abstract class MatchImpl implements Match {
|
|||
sb.append(" QUITTED");
|
||||
}
|
||||
sb.append("<br/>");
|
||||
if (mp.getDeck() != null) {
|
||||
sb.append("DeckHash: ").append(mp.getDeck().getDeckHashCode()).append("<br/>");
|
||||
}
|
||||
}
|
||||
if (getDraws() > 0) {
|
||||
sb.append(" Draws: ").append(getDraws()).append("<br/>");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.cards.Card;
|
|||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckValidator;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -12,13 +13,15 @@ import java.io.Serializable;
|
|||
*/
|
||||
public class MatchPlayer implements Serializable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MatchPlayer.class);
|
||||
|
||||
private int wins;
|
||||
private int winsNeeded;
|
||||
private boolean matchWinner;
|
||||
|
||||
private Deck deck;
|
||||
private Player player;
|
||||
private String name;
|
||||
private final Player player;
|
||||
private final String name;
|
||||
|
||||
private boolean quit;
|
||||
private boolean doneSideboarding;
|
||||
|
|
@ -89,22 +92,33 @@ public class MatchPlayer implements Serializable {
|
|||
return viewerDeck;
|
||||
}
|
||||
|
||||
public void submitDeck(Deck deck) {
|
||||
this.deck = deck;
|
||||
public void submitDeck(Deck newDeck) {
|
||||
this.deck = newDeck;
|
||||
this.doneSideboarding = true;
|
||||
}
|
||||
|
||||
public void updateDeck(Deck deck) {
|
||||
public boolean updateDeck(Deck newDeck) {
|
||||
// used for auto-save by timeout from client side
|
||||
|
||||
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||
if (this.deck != null) {
|
||||
// preserver deck name, important for Tiny Leaders format
|
||||
deck.setName(this.getDeck().getName());
|
||||
// preserve the original deck hash code before sideboarding to give no information if cards were swapped
|
||||
deck.setDeckHashCode(this.getDeck().getDeckHashCode());
|
||||
newDeck.setName(this.getDeck().getName());
|
||||
}
|
||||
this.deck = deck;
|
||||
|
||||
// make sure it's the same deck (player do not add or remove something)
|
||||
boolean isGood = (this.deck.getDeckHash() == newDeck.getDeckHash());
|
||||
if (!isGood) {
|
||||
logger.error("Found cheating player " + player.getName()
|
||||
+ " with changed deck, main " + newDeck.getCards().size() + ", side " + newDeck.getSideboard().size());
|
||||
newDeck.getCards().clear();
|
||||
newDeck.getSideboard().clear();
|
||||
}
|
||||
|
||||
this.deck = newDeck;
|
||||
return isGood;
|
||||
}
|
||||
|
||||
public Deck generateDeck(DeckValidator deckValidator) {
|
||||
public Deck autoCompleteDeck(DeckValidator deckValidator) {
|
||||
// auto complete deck
|
||||
while (deck.getMaindeckCards().size() < deckValidator.getDeckMinSize() && !deck.getSideboard().isEmpty()) {
|
||||
Card card = deck.getSideboard().iterator().next();
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public interface Tournament {
|
|||
|
||||
void submitDeck(UUID playerId, Deck deck);
|
||||
|
||||
boolean updateDeck(UUID playerId, Deck deck);
|
||||
void updateDeck(UUID playerId, Deck deck);
|
||||
|
||||
void autoSubmit(UUID playerId, Deck deck);
|
||||
|
||||
|
|
|
|||
|
|
@ -139,11 +139,13 @@ public abstract class TournamentImpl implements Tournament {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
if (players.containsKey(playerId)) {
|
||||
return players.get(playerId).updateDeck(deck);
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
TournamentPlayer player = players.getOrDefault(playerId, null);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
|
||||
player.updateDeck(deck);
|
||||
}
|
||||
|
||||
protected Round createRoundRandom() {
|
||||
|
|
|
|||
|
|
@ -93,18 +93,18 @@ public class TournamentPlayer {
|
|||
}
|
||||
|
||||
public boolean updateDeck(Deck deck) {
|
||||
// Check if the cards included in the deck are the same as in the original deck
|
||||
boolean validDeck = (getDeck().getDeckCompleteHashCode() == deck.getDeckCompleteHashCode());
|
||||
if (!validDeck) {
|
||||
// Clear the deck so the player cheating looses the game
|
||||
// TODO: inform other players about cheating?!
|
||||
logger.error("Found cheating player " + getPlayer().getName()
|
||||
// used for auto-save deck
|
||||
|
||||
// make sure it's the same deck (player do not add or remove something)
|
||||
boolean isGood = (this.getDeck().getDeckHash() == deck.getDeckHash());
|
||||
if (!isGood) {
|
||||
logger.error("Found cheating tourney player " + player.getName()
|
||||
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
||||
deck.getCards().clear();
|
||||
deck.getSideboard().clear();
|
||||
}
|
||||
this.deck = deck;
|
||||
return validDeck;
|
||||
return isGood;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,20 +1,36 @@
|
|||
package mage.util;
|
||||
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
* @author LevelX2, JayDi85
|
||||
*/
|
||||
public final class DeckUtil {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DeckUtil.class);
|
||||
|
||||
public static long fixedHash(String string) {
|
||||
/**
|
||||
* Find deck hash (main + sideboard)
|
||||
* It must be same after sideboard (do not depend on main/sb structure)
|
||||
*/
|
||||
public static long getDeckHash(List<String> mainCards, List<String> sideboardCards) {
|
||||
List<String> all = new ArrayList<>(mainCards);
|
||||
all.addAll(sideboardCards);
|
||||
Collections.sort(all);
|
||||
return getStringHash(all.toString());
|
||||
}
|
||||
|
||||
private static long getStringHash(String string) {
|
||||
long h = 1125899906842597L; // prime
|
||||
int len = string.length();
|
||||
|
||||
|
|
@ -42,4 +58,27 @@ public final class DeckUtil {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure it's the same deck (player do not add or remove something)
|
||||
*
|
||||
* @param newDeck will be clear on cheating
|
||||
*/
|
||||
public boolean checkDeckForModification(String checkInfo, Player player, Deck oldDeck, Deck newDeck) {
|
||||
boolean isGood = (oldDeck.getDeckHash() == newDeck.getDeckHash());
|
||||
if (!isGood) {
|
||||
String message = String.format("Found cheating player [%s] in [%s] with changed deck, main %d -> %d, side %d -> %d",
|
||||
player.getName(),
|
||||
checkInfo,
|
||||
oldDeck.getCards().size(),
|
||||
newDeck.getCards().size(),
|
||||
oldDeck.getSideboard().size(),
|
||||
newDeck.getSideboard().size()
|
||||
);
|
||||
logger.error(message);
|
||||
newDeck.getCards().clear();
|
||||
newDeck.getSideboard().clear();
|
||||
}
|
||||
return isGood;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue