From eae85e71bc26437a50002b3ac4169d8134f84fad Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 4 Jan 2018 01:35:33 +0400 Subject: [PATCH] UI: added new cheat engine for manual testing (see #4334) --- .../java/mage/server/game/GameController.java | 6 +- .../java/mage/server/util/SystemUtil.java | 308 +++++++++++++----- .../serverside/cheats/LoadCheatsTest.java | 63 ++++ 3 files changed, 295 insertions(+), 82 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index e7ceb47f8ab..d039beff8da 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -668,7 +668,7 @@ public class GameController implements GameCallback { } catch (GameException ex) { logger.warn(ex.getMessage()); } - addCardsForTesting(game); + addCardsForTesting(game, playerId); updateGame(); } @@ -919,8 +919,8 @@ public class GameController implements GameCallback { /** * Adds cards in player's hands that are specified in config/init.txt. */ - private void addCardsForTesting(Game game) { - SystemUtil.addCardsForTesting(game); + private void addCardsForTesting(Game game, UUID playerId) { + SystemUtil.addCardsForTesting(game, null, game.getPlayer(playerId)); } /** diff --git a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java index b07227c694f..94ec14c9997 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -5,19 +5,23 @@ import java.lang.reflect.Constructor; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import mage.cards.Card; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.util.RandomUtil; /** - * @author nantuko + * @author JayDi85 */ public final class SystemUtil { @@ -26,115 +30,261 @@ public final class SystemUtil { private static final String INIT_FILE_PATH = "config" + File.separator + "init.txt"; private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(SystemUtil.class); + private static final String COMMAND_MANA_ADD = "@mana add"; + private static final String COMMAND_LANDS_ADD = "@lands add"; + private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; + private static final String COMMAND_CLEAR_BATTLEFIELD = "@clear battlefield"; + private static final Map supportedCommands = new HashMap<>(); + static { + supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD"); + supportedCommands.put(COMMAND_LANDS_ADD, "LANDS ADD"); + supportedCommands.put(COMMAND_RUN_CUSTOM_CODE, "RUN CUSTOM CODE"); + supportedCommands.put(COMMAND_CLEAR_BATTLEFIELD, "CLAR BATTLEFIELD"); + } + + private static class CommandGroup{ + String name; + boolean isSpecialCommand; + ArrayList commands = new ArrayList<>(); + + public CommandGroup(String name){ + this(name, false); + } + + public CommandGroup(String name, boolean isSpecialCommand){ + this.name = name; + this.isSpecialCommand = isSpecialCommand; + } + + public String getPrintName(){ + if(this.isSpecialCommand && supportedCommands.containsKey(this.name)){ + return supportedCommands.get(this.name); + } else { + return this.name; + } + } + + public String getPrintNameWithStats(){ + String res = this.getPrintName(); + if(!this.isSpecialCommand){ + res = res + " (" + this.commands.size() + " commands)"; + } + + return res; + } + } + + public static void addCardsForTesting(Game game) { + addCardsForTesting(game, null, null); + } + /** * Replaces cards in player's hands by specified in config/init.txt.
*
* Implementation note:
* 1. Read init.txt line by line
- * 2. Parse line using the following format: line ::= + * 2. Parse line using for searching groups like: [group 1] + * 3. Parse line using the following format: line ::= * :::
- * 3. If zone equals to 'hand', add card to player's library
- * 3a. Then swap added card with any card in player's hand
- * 3b. Parse next line (go to 2.), If EOF go to 4.
- * 4. Log message to all players that cards were added (to prevent unfair + * 4. If zone equals to 'hand', add card to player's library
+ * 5a. Then swap added card with any card in player's hand
+ * 5b. Parse next line (go to 2.), If EOF go to 4.
+ * 6. Log message to all players that cards were added (to prevent unfair * play).
- * 5. Exit
+ * 7. Exit
* * @param game */ - public static void addCardsForTesting(Game game) { + public static void addCardsForTesting(Game game, String fileSource, Player feedbackPlayer) { + try { - File f = new File(INIT_FILE_PATH); + String fileName = fileSource; + if(fileName == null){ + fileName = INIT_FILE_PATH; + } + + File f = new File(fileName); if (!f.exists()) { - logger.warn("Couldn't find init file: " + INIT_FILE_PATH); + logger.warn("Couldn't find init file: " + fileName); return; } - logger.info("Parsing init.txt... "); + logger.info("Parsing init file... "); + + // steps: + // 1. parse groups and commands + // 2. ask user if many groups + // 3. run commands from selected group + + // 1. parse + Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] + Pattern patternCard = Pattern.compile("([a-zA-Z]+):([\\w]+):([a-zA-Z ,\\/\\-.!'\\d:]+?):(\\d+)"); // battlefield:Human:Island:10 + ArrayList groups = new ArrayList<>(); try (Scanner scanner = new Scanner(f)) { - Pattern pattern = Pattern.compile("([a-zA-Z]+):([\\w]+):([a-zA-Z ,\\/\\-.!'\\d:]+?):(\\d+)"); + + CommandGroup currentGroup = null; + while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); - if (line.isEmpty() || line.startsWith("#")) { + + // skip comments + if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) { continue; } - Matcher m = pattern.matcher(line); - if (!m.matches()) { - logger.warn("Init string wasn't parsed: " + line); - continue; + // group + Matcher matchGroup = patternGroup.matcher(line); + if (matchGroup.matches()) { + String groupName = matchGroup.group(1); + if(groupName.startsWith("@")){ + // special command group + if(supportedCommands.containsKey(groupName)){ + groups.add(new CommandGroup(groupName, true)); + }else { + logger.warn("Special group [" + groupName + "] is not supported."); + } + continue; + } else { + // basic group + currentGroup = new CommandGroup(groupName); + groups.add(currentGroup); + continue; + } } - String zone = m.group(1); - String nickname = m.group(2); - - Optional playerOptional = findPlayer(game, nickname); - if (!playerOptional.isPresent()) { - logger.warn("Was skipped because no player with that name: " + line); - continue; + // command + if (currentGroup == null) { + currentGroup = new CommandGroup("default group"); + groups.add(currentGroup); } - Player player = playerOptional.get(); + currentGroup.commands.add(line); + } + } - Zone gameZone; - if ("hand".equalsIgnoreCase(zone)) { - gameZone = Zone.HAND; - } else if ("battlefield".equalsIgnoreCase(zone)) { - gameZone = Zone.BATTLEFIELD; - } else if ("graveyard".equalsIgnoreCase(zone)) { - gameZone = Zone.GRAVEYARD; - } else if ("library".equalsIgnoreCase(zone)) { - gameZone = Zone.LIBRARY; - } else if ("token".equalsIgnoreCase(zone)) { - gameZone = Zone.BATTLEFIELD; + // 2. ask user + CommandGroup runGroup = null; + if(groups.size() == 1) { + // not need to ask + runGroup = groups.get(0); + }else if(groups.size() > 1) { + // need to ask + logger.info("Founded " + groups.size() + " groups. Need to select."); + + if(feedbackPlayer != null){ + // choice dialog + Map list = new LinkedHashMap<>(); + Map sort = new LinkedHashMap<>(); + for(Integer i = 0; i < groups.size(); i++){ + list.put(Integer.toString(i + 1), groups.get(i).getPrintNameWithStats()); + sort.put(Integer.toString(i + 1), i); + } + + Choice groupChoice = new ChoiceImpl(false); + groupChoice.setMessage("Choose commands group to run"); + groupChoice.setKeyChoices(list); + groupChoice.setSortData(sort); + + if (feedbackPlayer.choose(Outcome.Benefit, groupChoice, game)) { + String need = groupChoice.getChoiceKey(); + if ((need != null) && list.containsKey(need)) + { + runGroup = groups.get(Integer.parseInt(need) - 1); + } + } + }else{ + // select default + runGroup = groups.get(0); + } + + } + + if(runGroup == null) { + // was canceled + logger.info("Command file was empty or canceled"); + return; + } + + logger.info("Selected group [" + runGroup.name + "] with " + runGroup.commands.size() + " commands"); + + // 3. run commands + for (String line: runGroup.commands) { + + Matcher matchCommand = patternCard.matcher(line); + if (!matchCommand.matches()) { + logger.warn("Unknown command format: " + line); + continue; + } + + String zone = matchCommand.group(1); + String nickname = matchCommand.group(2); + + Optional playerOptional = findPlayer(game, nickname); + if (!playerOptional.isPresent()) { + logger.warn("Unknown player: " + line); + continue; + } + Player player = playerOptional.get(); + + Zone gameZone; + if ("hand".equalsIgnoreCase(zone)) { + gameZone = Zone.HAND; + } else if ("battlefield".equalsIgnoreCase(zone)) { + gameZone = Zone.BATTLEFIELD; + } else if ("graveyard".equalsIgnoreCase(zone)) { + gameZone = Zone.GRAVEYARD; + } else if ("library".equalsIgnoreCase(zone)) { + gameZone = Zone.LIBRARY; + } else if ("token".equalsIgnoreCase(zone)) { + gameZone = Zone.BATTLEFIELD; + } else if ("emblem".equalsIgnoreCase(zone)) { + gameZone = Zone.COMMAND; + } else { + logger.warn("Unknown zone: " + line); + continue; + } + + String cardName = matchCommand.group(3); + Integer amount = Integer.parseInt(matchCommand.group(4)); + + List cards = CardRepository.instance.findCards(cardName); + if (cards.isEmpty()) { + if ("token".equalsIgnoreCase(zone)) { + // eg: token:Human:HippoToken:1 + Class c = Class.forName("mage.game.permanent.token." + cardName); + Constructor cons = c.getConstructor(); + Object token = cons.newInstance(); + if (token != null && token instanceof mage.game.permanent.token.Token) { + ((mage.game.permanent.token.Token) token).putOntoBattlefield(amount, game, null, player.getId(), false, false); + continue; + } } else if ("emblem".equalsIgnoreCase(zone)) { - gameZone = Zone.COMMAND; - } else { - continue; // go parse next line - } - - String cardName = m.group(3); - Integer amount = Integer.parseInt(m.group(4)); - - List cards = CardRepository.instance.findCards(cardName); - if (cards.isEmpty()) { - if ("token".equalsIgnoreCase(zone)) { - // eg: token:Human:HippoToken:1 - Class c = Class.forName("mage.game.permanent.token." + cardName); - Constructor cons = c.getConstructor(); - Object token = cons.newInstance(); - if (token != null && token instanceof mage.game.permanent.token.Token) { - ((mage.game.permanent.token.Token) token).putOntoBattlefield(amount, game, null, player.getId(), false, false); - continue; - } - } else if ("emblem".equalsIgnoreCase(zone)) { - // eg: emblem:Human:ElspethSunsChampionEmblem:1 - Class c = Class.forName("mage.game.command.emblems." + cardName); - Constructor cons = c.getConstructor(); - Object emblem = cons.newInstance(); - if (emblem != null && emblem instanceof mage.game.command.Emblem) { - ((mage.game.command.Emblem) emblem).setControllerId(player.getId()); - game.addEmblem((mage.game.command.Emblem) emblem, null, player.getId()); - continue; - } - } - logger.warn("Couldn't find a card: " + cardName); - continue; - } - - Set cardsToLoad = new HashSet<>(); - for (int i = 0; i < amount; i++) { - CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); - Card card = cardInfo != null ? cardInfo.getCard() : null; - if (card != null) { - cardsToLoad.add(card); + // eg: emblem:Human:ElspethSunsChampionEmblem:1 + Class c = Class.forName("mage.game.command.emblems." + cardName); + Constructor cons = c.getConstructor(); + Object emblem = cons.newInstance(); + if (emblem != null && emblem instanceof mage.game.command.Emblem) { + ((mage.game.command.Emblem) emblem).setControllerId(player.getId()); + game.addEmblem((mage.game.command.Emblem) emblem, null, player.getId()); + continue; } } - game.loadCards(cardsToLoad, player.getId()); - for (Card card : cardsToLoad) { - swapWithAnyCard(game, player, card, gameZone); + logger.warn("Unknown card [" + cardName + "]: " + line); + continue; + } + + Set cardsToLoad = new HashSet<>(); + for (int i = 0; i < amount; i++) { + CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); + Card card = cardInfo != null ? cardInfo.getCard() : null; + if (card != null) { + cardsToLoad.add(card); } } + game.loadCards(cardsToLoad, player.getId()); + for (Card card : cardsToLoad) { + swapWithAnyCard(game, player, card, gameZone); + } } } catch (Exception e) { logger.fatal("", e); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java new file mode 100644 index 00000000000..68cb99f34aa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java @@ -0,0 +1,63 @@ +package org.mage.test.serverside.cheats; + +import mage.constants.*; +import mage.server.util.SystemUtil; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * @author JayDi85 + */ +public class LoadCheatsTest extends CardTestPlayerBase { + + private String prepareCommandFile(String data){ + try { + File commandFile = File.createTempFile("test-commands", ".txt"); + FileWriter w = new FileWriter(commandFile); + w.write(data); + w.close(); + return commandFile.getAbsolutePath(); + }catch (IOException e) { + Assert.fail("Can't create commands file: " + e.getMessage()); + return null; + } + } + + @Test + public void testCommands(){ + addCard(Zone.HAND, playerA, "Razorclaw Bear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + String commandsFile = prepareCommandFile(new StringBuilder() + .append("[group 1]").append('\n') + .append("battlefield:Human:Forest:10").append('\n') + .append("//battlefield:Human:Forest:10").append('\n') + .append("battlefield:Human:Island:10").append('\n') + .append("").append('\n') + .append("[@add mana]").append('\n') + .append("[group 2]").append('\n') + .append("//").append('\n') + .append("hand:Human:Island:10").append('\n') // need that + .append("[group 3]").append('\n') + .append("//").append('\n') + .append("hand:Human:Island:5").append('\n') + .toString() + .replace(":Human:", ":" + playerA.getName() + ":") + ); + + setChoice(playerA, "2"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + SystemUtil.addCardsForTesting(currentGame, commandsFile, playerA); + + assertHandCount(playerA, "Razorclaw Bear", 1); + assertPermanentCount(playerA, "Mountain", 3); + assertHandCount(playerA, "Island", 10); // by cheats + } +}