diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index f8b1bc543aa..048d7e89075 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -1633,7 +1633,42 @@ public class ComputerPlayer extends PlayerImpl implements Player { } public static Deck buildDeck(List cardPool, final List colors) { + return buildDeck(cardPool, colors, false); + } + + public static Deck buildDeck(List cardPool, final List colors, boolean onlyBasicLands) { + if (onlyBasicLands) { + return buildDeckWithOnlyBasicLands(cardPool); + } else { + return buildDeckWithNormalCards(cardPool, colors); + } + } + + public static Deck buildDeckWithOnlyBasicLands(List cardPool) { + // random cards from card pool Deck deck = new Deck(); + final int DECK_SIZE = 40; + + List sortedCards = new ArrayList<>(cardPool); + if (sortedCards.size() > 0) { + while (deck.getCards().size() < DECK_SIZE) { + deck.getCards().add(sortedCards.get(RandomUtil.nextInt(sortedCards.size()))); + } + return deck; + } else { + addBasicLands(deck, "Forest", DECK_SIZE); + return deck; + } + } + + public static Deck buildDeckWithNormalCards(List cardPool, final List colors) { + // top 23 cards plus basic lands until 40 deck size + Deck deck = new Deck(); + final int DECK_SIZE = 40; + final int DECK_CARDS_COUNT = 23; + final int DECK_LANDS_COUNT = DECK_SIZE - DECK_CARDS_COUNT; + + // sort card pool by top score List sortedCards = new ArrayList<>(cardPool); Collections.sort(sortedCards, new Comparator() { @Override @@ -1643,8 +1678,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { return score2.compareTo(score1); } }); + + // get top cards int cardNum = 0; - while (deck.getCards().size() < 23 && sortedCards.size() > cardNum) { + while (deck.getCards().size() < DECK_CARDS_COUNT && sortedCards.size() > cardNum) { Card card = sortedCards.get(cardNum); if (!card.isBasic()) { deck.getCards().add(card); @@ -1652,53 +1689,61 @@ public class ComputerPlayer extends PlayerImpl implements Player { } cardNum++; } - // add basic lands + + // add basic lands by color percent // TODO: compensate for non basic lands Mana mana = new Mana(); for (Card card : deck.getCards()) { mana.add(card.getManaCost().getMana()); } double total = mana.getBlack() + mana.getBlue() + mana.getGreen() + mana.getRed() + mana.getWhite(); + + // most frequent land is forest by defalt int mostLand = 0; String mostLandName = "Forest"; if (mana.getGreen() > 0) { - int number = (int) Math.round(mana.getGreen() / total * 17); + int number = (int) Math.round(mana.getGreen() / total * DECK_LANDS_COUNT); addBasicLands(deck, "Forest", number); mostLand = number; } + if (mana.getBlack() > 0) { - int number = (int) Math.round(mana.getBlack() / total * 17); + int number = (int) Math.round(mana.getBlack() / total * DECK_LANDS_COUNT); addBasicLands(deck, "Swamp", number); if (number > mostLand) { mostLand = number; mostLandName = "Swamp"; } } + if (mana.getBlue() > 0) { - int number = (int) Math.round(mana.getBlue() / total * 17); + int number = (int) Math.round(mana.getBlue() / total * DECK_LANDS_COUNT); addBasicLands(deck, "Island", number); if (number > mostLand) { mostLand = number; mostLandName = "Island"; } } + if (mana.getWhite() > 0) { - int number = (int) Math.round(mana.getWhite() / total * 17); + int number = (int) Math.round(mana.getWhite() / total * DECK_LANDS_COUNT); addBasicLands(deck, "Plains", number); if (number > mostLand) { mostLand = number; mostLandName = "Plains"; } } + if (mana.getRed() > 0) { - int number = (int) Math.round(mana.getRed() / total * 17); + int number = (int) Math.round(mana.getRed() / total * DECK_LANDS_COUNT); addBasicLands(deck, "Mountain", number); if (number > mostLand) { mostLandName = "Plains"; } } - addBasicLands(deck, mostLandName, 40 - deck.getCards().size()); + // adds remaining lands (most popular name) + addBasicLands(deck, mostLandName, DECK_SIZE - deck.getCards().size()); return deck; } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 96628efb215..9619fff72a2 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -74,6 +74,7 @@ import mage.target.common.TargetDefender; import mage.util.GameLog; import mage.util.ManaUtil; import mage.util.MessageToClient; +import mage.util.RandomUtil; import org.apache.log4j.Logger; /** @@ -82,6 +83,7 @@ import org.apache.log4j.Logger; */ public class HumanPlayer extends PlayerImpl { + private transient Boolean responseOpenedForAnswer = false; // can't get response until prepared target (e.g. until send all fire events to all players) private final transient PlayerResponse response = new PlayerResponse(); protected static FilterCreatureForCombatBlock filterCreatureForCombatBlock = new FilterCreatureForCombatBlock(); @@ -148,6 +150,17 @@ public class HumanPlayer extends PlayerImpl { || (actionIterations > 0 && !actionQueueSaved.isEmpty())); } + protected void waitResponseOpen() { + // wait response open for answer process + while(!responseOpenedForAnswer && canRespond()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + logger.warn("Response waiting interrapted for " + getId()); + } + } + } + protected boolean pullResponseFromQueue(Game game) { if (actionQueue.isEmpty() && actionIterations > 0 && !actionQueueSaved.isEmpty()) { actionQueue = new LinkedList(actionQueueSaved); @@ -164,6 +177,7 @@ public class HumanPlayer extends PlayerImpl { } sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_STACK_RESOLVED, game, null); } + waitResponseOpen(); synchronized (response) { response.copy(action); response.notifyAll(); @@ -174,6 +188,11 @@ public class HumanPlayer extends PlayerImpl { return false; } + protected void prepareForResponse(Game game) { + //logger.info("Prepare waiting " + getId()); + responseOpenedForAnswer = false; + } + protected void waitForResponse(Game game) { if (isExecutingMacro()) { pullResponseFromQueue(game); @@ -184,21 +203,27 @@ public class HumanPlayer extends PlayerImpl { // } return; } - response.clear(); - logger.debug("Waiting response from player: " + getId()); + response.clear(); // TODO: only one response for all games (can play only one game per session)?! + //logger.info("Waiting response from player: " + getId()); game.resumeTimer(getTurnControlledBy()); + responseOpenedForAnswer = true; // start waiting for next response boolean loop = true; while (loop) { loop = false; + synchronized (response) { try { + //logger.info("wait start: " + getId()); response.wait(); + //logger.info("wait end: " + getId()); } catch (InterruptedException ex) { logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex); } finally { + responseOpenedForAnswer = false; game.pauseTimer(getTurnControlledBy()); } } + if (response.getResponseConcedeCheck()) { ((GameImpl) game).checkConcede(); if (game.hasEnded()) { @@ -210,6 +235,7 @@ public class HumanPlayer extends PlayerImpl { } } } + //logger.info("Waiting response DONE (res queue " + actionQueueSaved.size() + "): " + getId() + ", res: " + response.toString()); if (recordingMacro && !macroTriggeredSelectionFlag) { // logger.info("Adding an action " + response); actionQueueSaved.add(new PlayerResponse(response)); @@ -227,6 +253,7 @@ public class HumanPlayer extends PlayerImpl { Map options = new HashMap<>(); options.put("UI.left.btn.text", "Mulligan"); options.put("UI.right.btn.text", "Keep"); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireAskPlayerEvent(playerId, new MessageToClient(message), null, options); } @@ -269,6 +296,7 @@ public class HumanPlayer extends PlayerImpl { if (messageToClient.getSecondMessage() == null) { messageToClient.setSecondMessage(getRelatedObjectName(source, game)); } + prepareForResponse(game); if (!isExecutingMacro()) { game.fireAskPlayerEvent(playerId, messageToClient, source, options); } @@ -330,10 +358,11 @@ public class HumanPlayer extends PlayerImpl { replacementEffectChoice.setKeyChoices(rEffects); while (!abort) { + updateGameStatePriority("chooseEffect", game); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireChooseChoiceEvent(playerId, replacementEffectChoice); } - updateGameStatePriority("chooseEffect", game); waitForResponse(game); logger.debug("Choose effect: " + response.getString()); if (response.getString() != null) { @@ -367,6 +396,7 @@ public class HumanPlayer extends PlayerImpl { } updateGameStatePriority("choose(3)", game); while (!abort) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireChooseChoiceEvent(playerId, choice); } @@ -416,6 +446,7 @@ public class HumanPlayer extends PlayerImpl { List chosen = target.getTargets(); options.put("chosen", (Serializable) chosen); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(sourceId, game)), targetIds, required, getOptions(target, options)); } @@ -483,6 +514,7 @@ public class HumanPlayer extends PlayerImpl { required = false; } + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargets, required, getOptions(target, null)); } @@ -553,6 +585,7 @@ public class HumanPlayer extends PlayerImpl { options.put("choosable", (Serializable) choosable); } + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options); } @@ -617,6 +650,7 @@ public class HumanPlayer extends PlayerImpl { if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options); } + prepareForResponse(game); waitForResponse(game); if (response.getUUID() != null) { if (target.getTargets().contains(response.getUUID())) { // if already included remove it @@ -643,6 +677,7 @@ public class HumanPlayer extends PlayerImpl { public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { updateGameStatePriority("chooseTargetAmount", game); while (!abort) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage() + "\n Amount remaining:" + target.getAmountRemaining(), getRelatedObjectName(source, game)), target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game), @@ -786,6 +821,7 @@ public class HumanPlayer extends PlayerImpl { while (canRespond()) { updateGameStatePriority("priority", game); holdingPriority = false; + prepareForResponse(game); if (!isExecutingMacro()) { game.firePriorityEvent(playerId); } @@ -920,6 +956,7 @@ public class HumanPlayer extends PlayerImpl { } macroTriggeredSelectionFlag = true; updateGameStatePriority("chooseTriggeredAbility", game); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetTriggeredAbilityEvent(playerId, "Pick triggered ability (goes to the stack first)", abilitiesWithNoOrderSet); } @@ -956,6 +993,7 @@ public class HumanPlayer extends PlayerImpl { protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { updateGameStatePriority("playMana", game); Map options = new HashMap<>(); + prepareForResponse(game); if (!isExecutingMacro()) { game.firePlayManaEvent(playerId, "Pay " + promptText, options); } @@ -992,6 +1030,7 @@ public class HumanPlayer extends PlayerImpl { int xValue = 0; updateGameStatePriority("announceRepetitions", game); do { + prepareForResponse(game); game.fireGetAmountEvent(playerId, "How many times do you want to repeat your shortcut?", 0, 999); waitForResponse(game); } while (response.getInteger() == null @@ -1018,6 +1057,7 @@ public class HumanPlayer extends PlayerImpl { int xValue = 0; updateGameStatePriority("announceXMana", game); do { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message, min, max); } @@ -1036,6 +1076,7 @@ public class HumanPlayer extends PlayerImpl { int xValue = 0; updateGameStatePriority("announceXCost", game); do { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message, min, max); } @@ -1107,6 +1148,7 @@ public class HumanPlayer extends PlayerImpl { options.put(Constants.Option.SPECIAL_BUTTON, (Serializable) "All attack"); } + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectEvent(playerId, "Select attackers", options); } @@ -1326,6 +1368,7 @@ public class HumanPlayer extends PlayerImpl { return; } while (!abort) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectEvent(playerId, "Select blockers"); } @@ -1358,6 +1401,7 @@ public class HumanPlayer extends PlayerImpl { public UUID chooseAttackerOrder(List attackers, Game game) { updateGameStatePriority("chooseAttackerOrder", game); while (!abort) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true); } @@ -1377,6 +1421,7 @@ public class HumanPlayer extends PlayerImpl { public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, List blockerOrder, Game game) { updateGameStatePriority("chooseBlockerOrder", game); while (!abort) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, "Pick blocker", blockers, true); } @@ -1395,6 +1440,7 @@ public class HumanPlayer extends PlayerImpl { protected void selectCombatGroup(UUID defenderId, UUID blockerId, Game game) { updateGameStatePriority("selectCombatGroup", game); TargetAttackingCreature target = new TargetAttackingCreature(); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)), target.possibleTargets(null, playerId, game), false, getOptions(target, null)); @@ -1447,6 +1493,7 @@ public class HumanPlayer extends PlayerImpl { public int getAmount(int min, int max, String message, Game game) { updateGameStatePriority("getAmount", game); do { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message, min, max); } @@ -1478,6 +1525,7 @@ public class HumanPlayer extends PlayerImpl { LinkedHashMap specialActions = game.getState().getSpecialActions().getControlledBy(playerId, false); if (!specialActions.isEmpty()) { updateGameStatePriority("specialAction", game); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values())); } @@ -1494,6 +1542,7 @@ public class HumanPlayer extends PlayerImpl { LinkedHashMap specialActions = game.getState().getSpecialActions().getControlledBy(playerId, true); if (!specialActions.isEmpty()) { updateGameStatePriority("specialAction", game); + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values())); } @@ -1539,6 +1588,7 @@ public class HumanPlayer extends PlayerImpl { } } + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(abilities.values())); } @@ -1579,6 +1629,7 @@ public class HumanPlayer extends PlayerImpl { return (SpellAbility) useableAbilities.values().iterator().next(); } else if (useableAbilities != null && !useableAbilities.isEmpty()) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values())); } @@ -1631,6 +1682,7 @@ public class HumanPlayer extends PlayerImpl { if (!modeMap.isEmpty()) { boolean done = false; while (!done) { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetModeEvent(playerId, "Choose Mode", modeMap); } @@ -1660,6 +1712,7 @@ public class HumanPlayer extends PlayerImpl { public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { updateGameStatePriority("choosePile", game); do { + prepareForResponse(game); if (!isExecutingMacro()) { game.fireChoosePileEvent(playerId, message, pile1, pile2); } @@ -1673,6 +1726,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void setResponseString(String responseString) { + waitResponseOpen(); synchronized (response) { response.setString(responseString); response.notifyAll(); @@ -1682,6 +1736,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void setResponseManaType(UUID manaTypePlayerId, ManaType manaType) { + waitResponseOpen(); synchronized (response) { response.setManaType(manaType); response.setResponseManaTypePlayerId(manaTypePlayerId); @@ -1692,6 +1747,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void setResponseUUID(UUID responseUUID) { + waitResponseOpen(); synchronized (response) { response.setUUID(responseUUID); response.notifyAll(); @@ -1701,15 +1757,17 @@ public class HumanPlayer extends PlayerImpl { @Override public void setResponseBoolean(Boolean responseBoolean) { + waitResponseOpen(); synchronized (response) { response.setBoolean(responseBoolean); response.notifyAll(); - logger.debug("Got response boolean from player: " + getId()); + logger.info("Got response boolean from player: " + getId()); } } @Override public void setResponseInteger(Integer responseInteger) { + waitResponseOpen(); synchronized (response) { response.setInteger(responseInteger); response.notifyAll(); @@ -1720,6 +1778,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void abort() { abort = true; + waitResponseOpen(); synchronized (response) { response.notifyAll(); logger.debug("Got cancel action from player: " + getId()); @@ -1728,6 +1787,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void signalPlayerConcede() { + waitResponseOpen(); synchronized (response) { response.setResponseConcedeCheck(); response.notifyAll(); @@ -1737,6 +1797,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void skip() { + waitResponseOpen(); synchronized (response) { response.setInteger(0); response.notifyAll(); diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java index 824efa60ba0..a5626cbd382 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java @@ -1,28 +1,32 @@ package org.mage.test.load; +import mage.constants.PhaseStep; +import mage.constants.PlayerAction; import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.remote.Session; +import mage.util.RandomUtil; import mage.utils.CompressUtil; -import mage.view.GameClientMessage; -import mage.view.GameView; -import mage.view.SimpleCardView; -import mage.view.TableClientMessage; +import mage.view.*; import org.apache.log4j.Logger; +import java.util.Random; import java.util.UUID; /** - * @author noxx + * @author JayDi85 */ public class LoadCallbackClient implements CallbackClient { - private static final Logger log = Logger.getLogger(LoadCallbackClient.class); + //private static final Logger log = Logger.getLogger(LoadCallbackClient.class); + private static final Logger log = Logger.getLogger("Load call"); private Session session; private UUID gameId; private UUID playerId; private boolean gameOver; + private String gameResult = "unknown"; + private boolean needToConcede = false; // will concede on first priority private volatile int controlCount; @@ -32,63 +36,152 @@ public class LoadCallbackClient implements CallbackClient { public void processCallback(ClientCallback callback) { //TODO controlCount = 0; - log.info(callback.getMethod()); callback.setData(CompressUtil.decompress(callback.getData())); + + /* + // random sleep can help with freezes (server concurrent access problem?!) + try { + Thread.sleep(RandomUtil.nextInt(1000)); + }catch (InterruptedException e) { + log.error("thread error", e); + } + */ + + log.info(callback.getMethod()); + log.info(getLogStartInfo() + "callback: " + callback.getMethod()); + switch (callback.getMethod()) { + case START_GAME: { TableClientMessage message = (TableClientMessage) callback.getData(); + log.info(getLogStartInfo() + "game started"); gameId = message.getGameId(); playerId = message.getPlayerId(); session.joinGame(message.getGameId()); startControlThread(); break; } - case GAME_INFORM: { + + case GAME_INFORM: + case GAME_INFORM_PERSONAL: { GameClientMessage message = (GameClientMessage) callback.getData(); - log.info("Inform: " + message.getMessage()); gameView = message.getGameView(); + log.info(getLogStartInfo() + "Inform: " + message.getMessage()); break; } - case GAME_INIT: - break; + case GAME_TARGET: { GameClientMessage message = (GameClientMessage) callback.getData(); + this.gameView = message.getGameView(); log.info("Target: " + message.getMessage()); switch (message.getMessage()) { case "Select a starting player": session.sendPlayerUUID(gameId, playerId); - break; + return; + //break; case "Select a card to discard": - log.info("hand size: " + gameView.getHand().size()); + log.info(getLogStartInfo() + "hand size: " + gameView.getHand().size()); SimpleCardView card = gameView.getHand().values().iterator().next(); session.sendPlayerUUID(gameId, card.getId()); - break; + return; + //break; + default: + log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString()); } break; } + case GAME_ASK: { GameClientMessage message = (GameClientMessage) callback.getData(); - log.info("Ask: " + message.getMessage()); - if (message.getMessage().equals("Do you want to take a mulligan?")) { + log.info(getLogStartInfo() + "Ask: " + message.getMessage()); + if (message.getMessage().startsWith("Mulligan")) { session.sendPlayerBoolean(gameId, false); + return; + } else { + log.error(getLogStartInfo() + "unknown GAME_ASK message: " + message.toString()); } break; } + case GAME_SELECT: { GameClientMessage message = (GameClientMessage) callback.getData(); log.info("Select: " + message.getMessage()); - if (LoadPhaseManager.getInstance().isSkip(message.getGameView(), message.getMessage(), playerId)) { - log.info("Skipped: " + message.getMessage()); - session.sendPlayerBoolean(gameId, false); + this.gameView = message.getGameView(); + + // concede + if (needToConcede) { + log.info(getLogStartInfo() + "game conceded"); + needToConcede = false; + session.sendPlayerAction(PlayerAction.CONCEDE, gameId, null); + return; } - break; + + // end priority step + session.sendPlayerBoolean(gameId, false); + return; +/* + if (LoadPhaseManager.getInstance().isSkip(message.getGameView(), message.getMessage(), playerId)) { + log.info(getLogStartInfo() + "Skipped: " + message.getMessage()); + session.sendPlayerBoolean(gameId, false); + } else { + log.error(getLogStartInfo() + "unknown GAME_SELECT or skips message: " + message.toString()); + } + */ + //break; } + case GAME_OVER: - log.info("Game over"); + log.info(getLogStartInfo() + "Game over"); gameOver = true; break; + + case END_GAME_INFO: + GameEndView message = (GameEndView) callback.getData(); + this.gameResult = message.hasWon() ? "win" : "lose"; + log.info(getLogStartInfo() + "Game end info, " + this.gameResult); + break; + + // skip callbacks (no need to react) + case GAME_INIT: + case GAME_UPDATE: + case CHATMESSAGE: + case JOINED_TABLE: + break; + + default: + log.error(getLogStartInfo() + "Unknown callback: " + callback.getMethod() + ", " + callback.getData().toString()); + session.sendPlayerBoolean(gameId, false); + return; + //break; + } + } + + private PlayerView getPlayer() { + if ((this.gameView != null) && (this.playerId != null)) { + for (PlayerView p: this.gameView.getPlayers()) { + if (p.getPlayerId().equals(this.playerId)) { + return p; + } + } + } + return null; + } + + private String getLogStartInfo() { + String mes = ""; + + //throw new IllegalArgumentException("test exception"); + + if (this.session != null) { + mes += session.getUserName() + ": "; } + PlayerView p = getPlayer(); + if (this.gameView != null && p != null && this.gameView.getStep() != null) { + mes += "T" + this.gameView.getTurn() + "-" + this.gameView.getStep().getIndex() + ", L:" + p.getLibraryCount() + ", H:" + getPlayer().getHandCount() + ": "; + } + + return mes; } public void setSession(Session session) { @@ -108,8 +201,13 @@ public class LoadCallbackClient implements CallbackClient { } catch (InterruptedException e) { e.printStackTrace(); } + + if(isGameOver()) { + return; + } + if (controlCount > 5) { - log.warn("Game seems freezed. Sending boolean message to server."); + log.warn(getLogStartInfo() + "Game seems freezed. Sending boolean message to server."); session.sendPlayerBoolean(gameId, false); controlCount = 0; } @@ -117,4 +215,12 @@ public class LoadCallbackClient implements CallbackClient { }).start(); } + + public void setConcede(boolean needToConcede) { + this.needToConcede = needToConcede; + } + + public String getLastGameResult() { + return this.gameResult; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java index 3d651c3144e..7a95f251135 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java @@ -1,13 +1,16 @@ package org.mage.test.load; +import mage.constants.PhaseStep; import mage.view.GameView; import mage.view.PlayerView; +import org.apache.log4j.Logger; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class LoadPhaseManager { + private static final Logger log = Logger.getLogger("Load phase"); private static final LoadPhaseManager instance = new LoadPhaseManager(); @@ -31,52 +34,63 @@ public class LoadPhaseManager { public static String MAIN_2_OTHERS = "main2Others"; public static String END_OF_TURN_OTHERS = "endOfTurnOthers"; - private static Map mapYou = new HashMap() {{ - put("Upkeep - play instants and activated abilities.", UPKEEP_YOU); - put("Draw - play instants and activated abilities.", DRAW_YOU); - put("Precombat Main - play spells and abilities.", MAIN_YOU); - put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_YOU); - put("End Combat - play instants and activated abilities.", END_OF_COMBAT_YOU); - put("Postcombat Main - play spells and abilities.", MAIN_2_YOU); - put("End Turn - play instants and activated abilities.", END_OF_TURN_YOU); - }}; + private static Map skipYou; + static { + skipYou = new HashMap() {{ + put(PhaseStep.UPKEEP, true); + put(PhaseStep.PRECOMBAT_MAIN, true); + put(PhaseStep.BEGIN_COMBAT, true); + put(PhaseStep.DECLARE_ATTACKERS, true); + put(PhaseStep.DECLARE_BLOCKERS, true); + put(PhaseStep.END_COMBAT, true); + put(PhaseStep.POSTCOMBAT_MAIN, true); + put(PhaseStep.END_TURN, true); + }}; + } - private static Map mapOthers = new HashMap() {{ - put("Upkeep - play instants and activated abilities.", UPKEEP_OTHERS); - put("Draw - play instants and activated abilities.", DRAW_OTHERS); - put("Precombat Main - play instants and activated abilities.", MAIN_OTHERS); - put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_OTHERS); - put("End Combat - play instants and activated abilities.", END_OF_COMBAT_OTHERS); - put("Postcombat Main - play instants and activated abilities.", MAIN_2_OTHERS); - put("End Turn - play instants and activated abilities.", END_OF_TURN_OTHERS); - }}; + private static Map skipOthers; + static { + skipYou = new HashMap() {{ + put(PhaseStep.UPKEEP, true); + put(PhaseStep.PRECOMBAT_MAIN, true); + put(PhaseStep.BEGIN_COMBAT, true); + put(PhaseStep.DECLARE_ATTACKERS, true); + put(PhaseStep.DECLARE_BLOCKERS, true); + put(PhaseStep.END_COMBAT, true); + put(PhaseStep.POSTCOMBAT_MAIN, true); + put(PhaseStep.END_TURN, true); + }}; + } public static LoadPhaseManager getInstance() { return instance; } public boolean isSkip(GameView gameView, String message, UUID playerId) { + // skip callbacks + UUID activePlayer = null; - Map map = mapOthers; + Map map = skipOthers; for (PlayerView playerView : gameView.getPlayers()) { if (playerView.isActive()) { activePlayer = playerView.getPlayerId(); if (activePlayer.equals(playerId)) { - map = mapYou; + map = skipYou; } } } + if (activePlayer == null) { throw new IllegalStateException("No active player found."); } - for (Map.Entry entry : map.entrySet()) { - if (message.equals(entry.getKey())) { - /*if (message.equals("Precombat Main - play spells and abilities.")) { - return false; - }*/ - return true; - } + + // PROCCESS + + if(map.containsKey(gameView.getStep())){ + return map.get(gameView.getStep()); + } else { + log.error("Unknown phase manager step: " + gameView.getStep().toString()); + return false; } - return false; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 240064072b4..68e8cf97503 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -7,26 +7,24 @@ import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; -import mage.constants.ColoredManaSymbol; -import mage.constants.MatchTimeLimit; -import mage.constants.MultiplayerAttackOption; -import mage.constants.RangeOfInfluence; +import mage.constants.*; import mage.game.match.MatchOptions; import mage.player.ai.ComputerPlayer; import mage.players.PlayerType; import mage.remote.Connection; +import mage.remote.MageRemoteException; import mage.remote.Session; import mage.remote.SessionImpl; -import mage.view.GameTypeView; -import mage.view.TableView; +import mage.util.RandomUtil; +import mage.view.*; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; /** * Intended to test Mage server under different load patterns. @@ -36,236 +34,296 @@ import java.util.UUID; * * Then it's also better to use -Xms256M -Xmx512M JVM options for these stests. * - * @author noxx + * @author JayDi85 */ + public class LoadTest { - /** - * Logger for tests - */ - private static final Logger log = Logger.getLogger(LoadTest.class); + private static final Logger logger = Logger.getLogger(LoadTest.class); - /** - * First player's username - */ - private static final String TEST_USER_NAME = "player"; - - /** - * Second player's username - */ - private static final String TEST_USER_NAME_2 = "opponent"; - - /** - * Server connection setting. - */ private static final String TEST_SERVER = "localhost"; - - /** - * Server connection setting. - */ private static final int TEST_PORT = 17171; - - /** - * Server connection setting. - */ private static final String TEST_PROXY_TYPE = "None"; + private static final String TEST_USER_NAME = "user"; - /** - * Determines how many times test will be executed in a row. - */ - private static final int EXECUTION_COUNT = 100; + @Test + public void test_CreateRandomDeck() { - /** - * Determines how many times test will be executed in a row. - */ - private static final int EXECUTION_COUNT_PLAY_GAME = 100; + Deck deck; + + deck = generateRandomDeck("G", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in G", + card.getColorIdentity().isGreen()); + } + + deck = generateRandomDeck("U", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in U", + card.getColorIdentity().isBlue()); + } + + deck = generateRandomDeck("BR", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in BR", + card.getColorIdentity().isBlack() || card.getColorIdentity().isRed()); + } + + deck = generateRandomDeck("BUG", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in BUG", + card.getColorIdentity().isBlack() || card.getColorIdentity().isBlue() || card.getColorIdentity().isGreen()); + } + + // lands + + deck = generateRandomDeck("UR", true); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in UR", + card.getColorIdentity().isBlue() || card.getColorIdentity().isRed()); + Assert.assertEquals("card " + card.getName() + " must be basic land ", Rarity.LAND, card.getRarity()); + } + + deck = generateRandomDeck("B", true); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in B", card.getColorIdentity().isBlack()); + Assert.assertEquals("card " + card.getName() + " must be basic land ", Rarity.LAND, card.getRarity()); + } + } - /** - * Tests connecting with two players, creating game and starting it. - * - * Executes the test EXECUTION_COUNT times. - * - * @throws Exception - */ @Test @Ignore - public void testStartGame() throws Exception { - DeckCardLists deckList = createDeck(); + public void test_UsersConnectToServer() throws Exception { - for (int i = 0; i < EXECUTION_COUNT; i++) { - Connection connection = createConnection(TEST_USER_NAME + i); + // simple connection to server - SimpleMageClient mageClient = new SimpleMageClient(); - Session session = new SessionImpl(mageClient); + // monitor other players + LoadPlayer monitor = new LoadPlayer("monitor"); + Assert.assertTrue(monitor.session.isConnected()); + int startUsersCount = monitor.getAllRoomUsers().size(); + int minimumSleepTime = 2000; - session.connect(connection); - UUID roomId = session.getMainRoomId(); + // user 1 + LoadPlayer player1 = new LoadPlayer("1"); + Thread.sleep(minimumSleepTime); + Assert.assertEquals("Can't see users count change 1", startUsersCount + 1, monitor.getAllRoomUsers().size()); + Assert.assertNotNull("Can't find user 1", monitor.findUser(player1.userName)); - GameTypeView gameTypeView = session.getGameTypes().get(0); - log.info("Game type view: " + gameTypeView.getName()); - MatchOptions options = createGameOptions(gameTypeView, session); + // user 2 + LoadPlayer player2 = new LoadPlayer("2"); + Thread.sleep(minimumSleepTime); + Assert.assertEquals("Can't see users count change 2", startUsersCount + 2, monitor.getAllRoomUsers().size()); + Assert.assertNotNull("Can't find user 2", monitor.findUser(player2.userName)); + } - TableView table = session.createTable(roomId, options); + @Test + @Ignore + public void test_TwoUsersPlayGameUntilEnd() { + // simple connection to server test - if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return; + // monitor other players + LoadPlayer monitor = new LoadPlayer("monitor"); + + // users + LoadPlayer player1 = new LoadPlayer("1"); + LoadPlayer player2 = new LoadPlayer("2"); + + // game by user 1 + GameTypeView gameType = player1.session.getGameTypes().get(0); + MatchOptions gameOptions = createSimpleGameOptions(gameType, player1.session); + TableView game = player1.session.createTable(player1.roomID, gameOptions); + UUID tableId = game.getTableId(); + Assert.assertEquals(player1.userName, game.getControllerName()); + + DeckCardLists deckList = createSimpleDeck("GR", true); + Optional checkGame; + + /* + for(DeckCardInfo info: deckList.getCards()) { + logger.info(info.getCardName()); + }*/ + + // before connect + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals("", checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals("", checkGame.get().getSeats().get(1).getPlayerName()); + + // connect user 1 + Assert.assertTrue(player1.session.joinTable(player1.roomID, tableId, player1.userName, PlayerType.HUMAN, 1, deckList, "")); + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals(player1.userName, checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals("", checkGame.get().getSeats().get(1).getPlayerName()); + + // connect user 2 + Assert.assertTrue(player2.session.joinTable(player2.roomID, tableId, player2.userName, PlayerType.HUMAN, 1, deckList, "")); + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals(player1.userName, checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals(player2.userName, checkGame.get().getSeats().get(1).getPlayerName()); + + // match start + Assert.assertTrue(player1.session.startMatch(player1.roomID, tableId)); + + // playing until game over + while(!player1.client.isGameOver() && !player2.client.isGameOver()) { + checkGame = monitor.getTable(tableId); + logger.warn(checkGame.get().getTableState()); + try { + Thread.sleep(1000); + } catch (InterruptedException e){ + logger.error(e.getMessage(), e); + } + } + } + + @Test + @Ignore + public void test_GameThread() { + // simple game thread to the end + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + + while (game.isPlaying()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + + Assert.assertEquals("finished", game.gameResult); + } + + @Test + @Ignore + public void test_GameThreadWithAbort() { + // simple game thread with game abort + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + game.gameEnd(true); // abort -- close client thread + Assert.assertEquals("aborted", game.gameResult); + } + + @Test + @Ignore + public void test_GameThreadWithConcede() { + // simple game thread with game abort + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + + try { + Thread.sleep(3000); + } catch (Throwable e) { + // + } + game.gameConcede(1); + game.gameWaitToStop(); + Assert.assertEquals("finished", game.gameResult); + Assert.assertEquals("lose", game.player1.lastGameResult); + Assert.assertEquals("win", game.player2.lastGameResult); + } + + @Test + @Ignore + public void test_MultipleGames() { + // multiple games until finish + + Instant startTime = Instant.now(); + + int MAX_GAMES = 30; + + // creating + logger.info("creating games..."); + ArrayList gamesList = new ArrayList<>(); + for(int i = 1; i <= MAX_GAMES; i++) { + gamesList.add(new LoadGame( + "game" + i, + "game" + i, + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + )); + } + logger.info("created " + gamesList.size() + " games"); + + // running + for(LoadGame game: gamesList) { + game.gameStart(); + } + logger.info("run " + gamesList.size() + " games"); + + // waiting + while (true) { + boolean isComplete = true; + for(LoadGame game: gamesList) { + isComplete = isComplete && !game.isPlaying(); } - /*** Connect with a second player ***/ - Connection connection2 = createConnection(TEST_USER_NAME_2 + i); - SimpleMageClient mageClient2 = new SimpleMageClient(); - Session session2 = new SessionImpl(mageClient2); - session2.connect(connection2); - UUID roomId2 = session2.getMainRoomId(); - - // connect to the table with the same deck - if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return; + if(isComplete) { + break; } - /*** Start game ***/ - session.startMatch(roomId, table.getTableId()); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + logger.info("completed " + gamesList.size() + " games"); - Thread.sleep(100); + Instant endTime = Instant.now(); + logger.info("total time: " + ChronoUnit.SECONDS.between(startTime, endTime) + " secs"); + + // check statuses + ArrayList errors = new ArrayList<>(); + for(LoadGame game: gamesList) { + if (!"finished".equals(game.gameResult)) { + errors.add(game.gameName + ": not finished, got " + game.gameResult); + } + } + + if (errors.size() > 0) { + System.out.println("Not all games finished, founded " + errors.size() + " errors: "); + for (String s: errors) { + System.out.println(s); + } + Assert.fail("Not all games finished"); } } - /** - * Tests 10 simple games played one after another. - */ - @Test - @Ignore - public void testSimpleGame() throws Exception { - final DeckCardLists deckList = createDeck(); - - for (int i = 0; i < EXECUTION_COUNT_PLAY_GAME; i++) { - final int j = i; - Thread t = new Thread(() -> { - try { - testSimpleGame0(deckList, j); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - t.start(); - t.join(); - } - } - - /** - * Tests simple game till the end (game over). - * Players do nothing but skip phases and discard cards at the end. - * - * This results in a game that lasts until there is no cards in library. - */ - private boolean testSimpleGame0(DeckCardLists deckList, int i) throws InterruptedException { - Connection connection = createConnection(TEST_USER_NAME + i); - - SimpleMageClient mageClient = new SimpleMageClient(); - Session session = new SessionImpl(mageClient); - - session.connect(connection); - - mageClient.setSession(session); - UUID roomId = session.getMainRoomId(); - - GameTypeView gameTypeView = session.getGameTypes().get(0); - log.info("Game type view: " + gameTypeView.getName()); - MatchOptions options = createGameOptions(gameTypeView, session); - - TableView table = session.createTable(roomId, options); - - if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return true; - } - - /*** Connect with a second player ***/ - Connection connection2 = createConnection(TEST_USER_NAME_2 + i); - SimpleMageClient mageClient2 = new SimpleMageClient(); - Session session2 = new SessionImpl(mageClient2); - session2.connect(connection2); - - mageClient2.setSession(session2); - UUID roomId2 = session2.getMainRoomId(); - - // connect to the table with the same deck - if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return true; - } - - /*** Start game ***/ - session.startMatch(roomId, table.getTableId()); - - while (!mageClient.isGameOver()) { - Thread.sleep(1000); - } - return false; - } - - /** - * Tests playing the whole game. - * Player use cheat to add lands, creatures and other cards. - * Then play only lands, one of them plays 1 damage targeting player. - * - * This results in 40 turns of the game. - */ - @Test - @Ignore - public void testPlayGame() throws Exception { - //TODO: to be implemented - } - - /** - * Creates connection to the server. - * Server should run independently. - * - * @param username - * @return - */ - private Connection createConnection(String username) { - Connection connection = new Connection(); - connection.setUsername(username); - connection.setHost(TEST_SERVER); - connection.setPort(TEST_PORT); + private Connection createSimpleConnection(String username) { + Connection con = new Connection(); + con.setUsername(username); + con.setHost(TEST_SERVER); + con.setPort(TEST_PORT); Connection.ProxyType proxyType = Connection.ProxyType.valueByText(TEST_PROXY_TYPE); - connection.setProxyType(proxyType); - return connection; + con.setProxyType(proxyType); + return con; } - /** - * Returns random deck. - * Converts deck returned by {@link #generateRandomDeck} method to {@link DeckCardLists} format. - * - * @return - */ - private DeckCardLists createDeck() { - DeckCardLists deckList = new DeckCardLists(); - Deck deck = generateRandomDeck(); - for (Card card : deck.getCards()) { - CardInfo cardInfo = CardRepository.instance.findCard(card.getExpansionSetCode(), card.getCardNumber()); - if (cardInfo != null) { - deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); - } - } - return deckList; - } - - /** - * Creates game options with two human players. - * - * @param gameTypeView - * @param session - * @return - */ - private MatchOptions createGameOptions(GameTypeView gameTypeView, Session session) { + private MatchOptions createSimpleGameOptions(GameTypeView gameTypeView, Session session) { MatchOptions options = new MatchOptions("Test game", gameTypeView.getName(), false, 2); options.getPlayerTypes().add(PlayerType.HUMAN); @@ -280,21 +338,224 @@ public class LoadTest { return options; } - /** - * Generates random deck in {@link Deck} format. - * Uses {B}{R} as deck colors. - * - * @return - */ - private Deck generateRandomDeck() { - String selectedColors = "BR"; + private Deck generateRandomDeck(String colors, boolean onlyBasicLands) { + logger.info("Building " + (onlyBasicLands ? "only lands" : "random") + " deck with colors: " + colors); + List allowedColors = new ArrayList<>(); - log.info("Building deck with colors: " + selectedColors); - for (int i = 0; i < selectedColors.length(); i++) { - char c = selectedColors.charAt(i); + for (int i = 0; i < colors.length(); i++) { + char c = colors.charAt(i); allowedColors.add(ColoredManaSymbol.lookup(c)); } - List cardPool = Sets.generateRandomCardPool(45, allowedColors); - return ComputerPlayer.buildDeck(cardPool, allowedColors); + List cardPool = Sets.generateRandomCardPool(45, allowedColors, onlyBasicLands); + return ComputerPlayer.buildDeck(cardPool, allowedColors, onlyBasicLands); + } + + private DeckCardLists createSimpleDeck(String colors, boolean onlyBasicLands) { + Deck deck = generateRandomDeck(colors, onlyBasicLands); + + DeckCardLists deckList = new DeckCardLists(); + for (Card card : deck.getCards()) { + CardInfo cardInfo = CardRepository.instance.findCard(card.getExpansionSetCode(), card.getCardNumber()); + if (cardInfo != null) { + deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); + } + } + return deckList; + } + + private class LoadPlayer { + String userName; + Connection connection; + SimpleMageClient client; + Session session; + UUID roomID; + UUID createdTableID; + UUID connectedTableID; + DeckCardLists deckList; + String lastGameResult = ""; + + public LoadPlayer(String userPrefix) { + this.userName = TEST_USER_NAME + "_" + userPrefix + "_" + RandomUtil.nextInt(10000); + this.connection = createSimpleConnection(this.userName); + this.client = new SimpleMageClient(); + this.session = new SessionImpl(this.client); + + this.session.connect(this.connection); + this.client.setSession(this.session); + this.roomID = this.session.getMainRoomId(); + } + + public ArrayList getAllRoomUsers() { + ArrayList res = new ArrayList<>(); + try { + for (RoomUsersView roomUsers : this.session.getRoomUsers(this.roomID)) { + res.addAll(roomUsers.getUsersView()); + } + } catch (MageRemoteException e) + { + logger.error(e); + } + return res; + } + + public UsersView findUser(String userName) { + for (UsersView user: this.getAllRoomUsers()) { + if (user.getUserName().equals(userName)) { + return user; + } + } + return null; + } + + public Optional getTable(UUID tableID) { + return this.session.getTable(this.roomID, tableID); + } + + public UUID createNewTable() { + GameTypeView gameType = this.session.getGameTypes().get(0); + MatchOptions gameOptions = createSimpleGameOptions(gameType, this.session); + TableView game = this.session.createTable(this.roomID, gameOptions); + this.createdTableID = game.getTableId(); + Assert.assertEquals(this.userName, game.getControllerName()); + + connectToTable(this.createdTableID); + + return this.createdTableID; + } + + public void connectToTable(UUID tableID) { + Assert.assertTrue(this.session.joinTable(this.roomID, tableID, this.userName, PlayerType.HUMAN, 1, this.deckList, "")); + this.connectedTableID = tableID; + } + + public void startMatch() { + Assert.assertNotNull(this.createdTableID); + Assert.assertTrue(this.session.startMatch(this.roomID, this.createdTableID)); + } + + public void setDeckList(DeckCardLists deckList) { + this.deckList = deckList; + } + + public void disconnect() { + this.session.disconnect(false); + } + + public void concede() { + this.client.setConcede(true); + } + } + + private class LoadGame { + String gameName = null; + Thread runningThread = null; + LoadPlayer player1 = null; + LoadPlayer player2 = null; + Boolean abort = false; + UUID tableID = null; + String gameResult = null; + + public LoadGame(String gameName, String playerPrefix) { + this(gameName, playerPrefix, + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + } + + public LoadGame(String gameName, String playerPrefix, DeckCardLists deck1, DeckCardLists deck2) { + this.gameName = gameName; + + player1 = new LoadPlayer(playerPrefix + "_" + 1); + player1.setDeckList(deck1); + + player2 = new LoadPlayer(playerPrefix + "_" + 2); + player2.setDeckList(deck2); + } + + public void gameStart() { + + this.abort = false; + + runningThread = new Thread(() -> { + try { + this.tableID = this.player1.createNewTable(); + Assert.assertNotNull(this.tableID); + this.player2.connectToTable(this.tableID); + this.gameResult = "prepared"; + + this.player1.startMatch(); + this.gameResult = "started"; + + // playing until game over or abort + while(!abort && (!player1.client.isGameOver() || !player2.client.isGameOver())) { + try { + Thread.sleep(1000); + } catch (InterruptedException e){ + logger.error(e.getMessage(), e); + } + } + + // game results + if (abort) { + this.gameResult = "aborted"; + } else { + this.gameResult = "finished"; + } + player1.lastGameResult = player1.client.getLastGameResult(); + player2.lastGameResult = player2.client.getLastGameResult(); + } catch (Throwable e) { + this.gameResult = "error"; + logger.fatal("Game thread " + this.gameName + " was stopped by error"); + e.printStackTrace(); + } + + // disconnect on end + this.player1.disconnect(); + this.player2.disconnect(); + + // clean up after game + this.runningThread = null; + this.tableID = null; + }); + + runningThread.start(); + } + + public void gameEnd() { + gameEnd(false); + } + + public void gameEnd(Boolean waitToStop) { + this.abort = true; + + if (waitToStop) { + gameWaitToStop(); + } + } + + public void gameConcede(int playerNumber) { + switch (playerNumber) { + case 1: + this.player1.concede(); + break; + case 2: + this.player2.concede(); + break; + } + } + + public void gameWaitToStop() { + while (this.runningThread != null) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + } + + public Boolean isPlaying() { + return (this.runningThread != null); + } } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java index 7dcca370c6b..369e9671ce0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java @@ -11,7 +11,7 @@ import org.apache.log4j.Logger; /** * For tests only * - * @author noxx + * @author noxx, JayDi85 */ public class SimpleMageClient implements MageClient { @@ -20,7 +20,7 @@ public class SimpleMageClient implements MageClient { private static final Logger log = Logger.getLogger(SimpleMageClient.class); - private final CallbackClient callbackClient; + private final LoadCallbackClient callbackClient; public SimpleMageClient() { clientId = UUID.randomUUID(); @@ -54,7 +54,11 @@ public class SimpleMageClient implements MageClient { @Override public void processCallback(ClientCallback callback) { - callbackClient.processCallback(callback); + try { + callbackClient.processCallback(callback); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } } public void setSession(Session session) { @@ -64,4 +68,12 @@ public class SimpleMageClient implements MageClient { public boolean isGameOver() { return ((LoadCallbackClient)callbackClient).isGameOver(); } + + public void setConcede(boolean needToConcede) { + this.callbackClient.setConcede(needToConcede); + } + + public String getLastGameResult() { + return this.callbackClient.getLastGameResult(); + } } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 0cbe7af6e2a..5bcd0ff0398 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -111,12 +111,14 @@ public class ContinuousEffects implements Serializable { costModificationEffects = effect.costModificationEffects.copy(); spliceCardEffects = effect.spliceCardEffects.copy(); - temporaryEffects.putAll(effect.temporaryEffects); + for (Map.Entry> entry: effect.temporaryEffects.entrySet()) { + temporaryEffects.put(entry.getKey().copy(), entry.getValue()); + } collectAllEffects(); order = effect.order; } - private void collectAllEffects() { + private synchronized void collectAllEffects() { allEffectsLists.add(layeredEffects); allEffectsLists.add(continuousRuleModifyingEffects); allEffectsLists.add(replacementEffects); @@ -143,7 +145,7 @@ public class ContinuousEffects implements Serializable { return restrictionEffects; } - public void removeEndOfCombatEffects() { + public synchronized void removeEndOfCombatEffects() { layeredEffects.removeEndOfCombatEffects(); continuousRuleModifyingEffects.removeEndOfCombatEffects(); replacementEffects.removeEndOfCombatEffects(); @@ -157,7 +159,7 @@ public class ContinuousEffects implements Serializable { spliceCardEffects.removeEndOfCombatEffects(); } - public void removeEndOfTurnEffects() { + public synchronized void removeEndOfTurnEffects() { layeredEffects.removeEndOfTurnEffects(); continuousRuleModifyingEffects.removeEndOfTurnEffects(); replacementEffects.removeEndOfTurnEffects(); @@ -171,7 +173,7 @@ public class ContinuousEffects implements Serializable { spliceCardEffects.removeEndOfTurnEffects(); } - public void removeInactiveEffects(Game game) { + public synchronized void removeInactiveEffects(Game game) { layeredEffects.removeInactiveEffects(game); continuousRuleModifyingEffects.removeInactiveEffects(game); replacementEffects.removeInactiveEffects(game); @@ -186,7 +188,7 @@ public class ContinuousEffects implements Serializable { spliceCardEffects.removeInactiveEffects(game); } - public List getLayeredEffects(Game game) { + public synchronized List getLayeredEffects(Game game) { List layerEffects = new ArrayList<>(); for (ContinuousEffect effect : layeredEffects) { switch (effect.getDuration()) { @@ -226,7 +228,7 @@ public class ContinuousEffects implements Serializable { * * @param layerEffects */ - private void updateTimestamps(List layerEffects) { + private synchronized void updateTimestamps(List layerEffects) { for (ContinuousEffect continuousEffect : layerEffects) { // check if it's new, then set order if (!previous.contains(continuousEffect)) { @@ -857,7 +859,7 @@ public class ContinuousEffects implements Serializable { } //20091005 - 613 - public void apply(Game game) { + public synchronized void apply(Game game) { removeInactiveEffects(game); List activeLayerEffects = getLayeredEffects(game); @@ -1071,7 +1073,7 @@ public class ContinuousEffects implements Serializable { * @param sourceId * @param source */ - public void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) { + public synchronized void addEffect(ContinuousEffect effect, UUID sourceId, Ability source) { if (!(source instanceof MageSingleton)) { // because MageSingletons may never be removed by removing the temporary effecs they are not added to the temporaryEffects to prevent this effect.setTemporary(true); Set abilities = temporaryEffects.get(effect); @@ -1089,7 +1091,7 @@ public class ContinuousEffects implements Serializable { addEffect(effect, source); } - public void addEffect(ContinuousEffect effect, Ability source) { + public synchronized void addEffect(ContinuousEffect effect, Ability source) { if (effect == null) { logger.error("Effect is null: " + source.toString()); return; @@ -1167,14 +1169,14 @@ public class ContinuousEffects implements Serializable { } } - public void clear() { + public synchronized void clear() { for (ContinuousEffectsList effectsList : allEffectsLists) { effectsList.clear(); } temporaryEffects.clear(); } - public void removeAllTemporaryEffects() { + public synchronized void removeAllTemporaryEffects() { for (Map.Entry> entry : temporaryEffects.entrySet()) { switch (entry.getKey().getEffectType()) { case REPLACEMENT: diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 32084e9731f..a973990ce62 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -27,6 +27,7 @@ */ package mage.cards; +import mage.Mana; import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLayout; import mage.cards.decks.DeckCardLists; @@ -35,9 +36,12 @@ import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; +import mage.constants.Rarity; +import mage.filter.FilterMana; import mage.util.ClassScanner; import mage.util.RandomUtil; import org.apache.log4j.Logger; +import org.junit.Assert; import java.io.FileNotFoundException; import java.io.PrintWriter; @@ -45,7 +49,7 @@ import java.util.*; /** * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class Sets extends HashMap { @@ -93,23 +97,50 @@ public class Sets extends HashMap { * @return */ public static List generateRandomCardPool(int cardsCount, List allowedColors) { + return generateRandomCardPool(cardsCount, allowedColors, false); + } + + public static List generateRandomCardPool(int cardsCount, List allowedColors, boolean onlyBasicLands) { CardCriteria criteria = new CardCriteria(); - criteria.notTypes(CardType.LAND); + + if (onlyBasicLands) { + // only lands + criteria.rarities(Rarity.LAND); + criteria.colorless(true); // basic lands is colorless + } else { + // any card, but not basic lands + criteria.notTypes(CardType.LAND); + + // clear colors + criteria.white(false); + criteria.blue(false); + criteria.black(false); + criteria.red(false); + criteria.green(false); + criteria.colorless(false); // colorless is not allowed for gen + } + + FilterMana manaNeed = new FilterMana(); for (ColoredManaSymbol color : allowedColors) { switch (color) { case W: + manaNeed.setWhite(true); criteria.white(true); break; case U: + manaNeed.setBlue(true); criteria.blue(true); break; case B: + manaNeed.setBlack(true); criteria.black(true); break; case R: + manaNeed.setRed(true); criteria.red(true); break; case G: + manaNeed.setGreen(true); criteria.green(true); break; } @@ -123,9 +154,37 @@ public class Sets extends HashMap { CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); Card card = cardInfo != null ? cardInfo.getCard() : null; if (card != null) { - cardPool.add(card); - count++; + + FilterMana manaCard = card.getColorIdentity(); + boolean cardManaOK = true; + + if (onlyBasicLands) { + // lands is colorless + // discard not needed color by mana produce + Assert.assertEquals("only basic lands allow", 1, card.getMana().size()); + for (Mana manaLand : card.getMana()) { + if (manaLand.getWhite() > 0 && !manaNeed.isWhite()) { cardManaOK = false; } + if (manaLand.getBlue() > 0 && !manaNeed.isBlue()) { cardManaOK = false; } + if (manaLand.getBlack() > 0 && !manaNeed.isBlack()) { cardManaOK = false; } + if (manaLand.getRed() > 0 && !manaNeed.isRed()) { cardManaOK = false; } + if (manaLand.getGreen() > 0 && !manaNeed.isGreen()) { cardManaOK = false; } + } + } else { + // cards + // discard any card that have not needed color + if (manaCard.isWhite() && !manaNeed.isWhite()) { cardManaOK = false; } + if (manaCard.isBlue() && !manaNeed.isBlue()) { cardManaOK = false; } + if (manaCard.isBlack() && !manaNeed.isBlack()) { cardManaOK = false; } + if (manaCard.isRed() && !manaNeed.isRed()) { cardManaOK = false; } + if (manaCard.isGreen() && !manaNeed.isGreen()) { cardManaOK = false; } + } + + if (cardManaOK) { + cardPool.add(card); + count++; + } } + tries++; if (tries > 4096) { // to avoid infinite loop throw new IllegalStateException("Not enough cards for chosen colors to generate deck: " + allowedColors); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index b28ba370b57..051afdd9f86 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3773,4 +3773,22 @@ public abstract class PlayerImpl implements Player, Serializable { public List getDesignations() { return designations; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + PlayerImpl obj = (PlayerImpl) o; + if (this.getId() == null || obj.getId() == null) { + return false; + } + + return this.getId().equals(obj.getId()); + } }