package org.mage.test.serverside.base; import mage.cards.Card; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.Game; import mage.game.match.MatchType; import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; import mage.players.Player; import mage.players.PlayerType; import mage.server.game.GameFactory; import mage.server.game.PlayerFactory; import mage.server.tournament.TournamentFactory; import mage.server.util.ConfigSettings; import mage.server.util.PluginClassLoader; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; import mage.util.Copier; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.BeforeClass; import org.mage.test.player.RandomPlayer; import org.mage.test.player.TestPlayer; import java.io.File; import java.io.FileNotFoundException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Base class for all tests. * * @author ayratn */ public abstract class MageTestBase { protected static Logger logger = Logger.getLogger(MageTestBase.class); public static PluginClassLoader classLoader = new PluginClassLoader(); private static final String pluginFolder = "plugins"; protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)(:\\{tapped\\})?"); protected List<Card> handCardsA = new ArrayList<>(); protected List<Card> handCardsB = new ArrayList<>(); protected List<PermanentCard> battlefieldCardsA = new ArrayList<>(); protected List<PermanentCard> battlefieldCardsB = new ArrayList<>(); protected List<Card> graveyardCardsA = new ArrayList<>(); protected List<Card> graveyardCardsB = new ArrayList<>(); protected List<Card> libraryCardsA = new ArrayList<>(); protected List<Card> libraryCardsB = new ArrayList<>(); protected Map<Zone, String> commandsA = new HashMap<>(); protected Map<Zone, String> commandsB = new HashMap<>(); protected TestPlayer playerA; protected TestPlayer playerB; /** * Game instance initialized in load method. */ protected static Game currentGame = null; /** * Player thats starts the game first. By default, it is ComputerA. */ protected static Player activePlayer = null; protected Integer stopOnTurn; protected PhaseStep stopAtStep = PhaseStep.UNTAP; protected enum ParserState { INIT, OPTIONS, EXPECTED } protected ParserState parserState; /** * Expected results of the test. Read from test case in {@link String} based * format: * <p/> * Example: turn:1 result:won:ComputerA life:ComputerA:20 life:ComputerB:0 * battlefield:ComputerB:Tine Shrike:0 graveyard:ComputerB:Tine Shrike:1 */ protected List<String> expectedResults = new ArrayList<>(); protected static final String TESTS_PATH = "tests" + File.separator; @BeforeClass public static void init() { Logger.getRootLogger().setLevel(Level.DEBUG); logger.info("Starting MAGE tests"); logger.info("Logging level: " + logger.getLevel()); deleteSavedGames(); ConfigSettings config = ConfigSettings.instance; for (GamePlugin plugin : config.getGameTypes()) { GameFactory.instance.addGameType(plugin.getName(), loadGameType(plugin), loadPlugin(plugin)); } for (GamePlugin plugin : config.getTournamentTypes()) { TournamentFactory.instance.addTournamentType(plugin.getName(), loadTournamentType(plugin), loadPlugin(plugin)); } for (Plugin plugin : config.getPlayerTypes()) { PlayerFactory.instance.addPlayerType(plugin.getName(), loadPlugin(plugin)); } // for (Plugin plugin : config.getDeckTypes()) { // DeckValidatorFactory.getInstance().addDeckType(plugin.getName(), loadPlugin(plugin)); // } Copier.setLoader(classLoader); } private static Class<?> loadPlugin(Plugin plugin) { try { classLoader.addURL(new File(pluginFolder + '/' + plugin.getJar()).toURI().toURL()); logger.info("Loading plugin: " + plugin.getClassName()); return Class.forName(plugin.getClassName(), true, classLoader); } catch (ClassNotFoundException ex) { logger.warn("Plugin not Found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { logger.fatal("Error loading plugin " + plugin.getJar(), ex); } return null; } private static MatchType loadGameType(GamePlugin plugin) { try { classLoader.addURL(new File(pluginFolder + '/' + plugin.getJar()).toURI().toURL()); logger.info("Loading game type: " + plugin.getClassName()); return (MatchType) Class.forName(plugin.getTypeName(), true, classLoader).getConstructor().newInstance(); } catch (ClassNotFoundException ex) { logger.warn("Game type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } private static TournamentType loadTournamentType(GamePlugin plugin) { try { classLoader.addURL(new File(pluginFolder + '/' + plugin.getJar()).toURI().toURL()); logger.info("Loading tournament type: " + plugin.getClassName()); return (TournamentType) Class.forName(plugin.getTypeName(), true, classLoader).getConstructor().newInstance(); } catch (ClassNotFoundException ex) { logger.warn("Tournament type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } private static void deleteSavedGames() { File directory = new File("saved/"); if (!directory.exists()) { directory.mkdirs(); } File[] files = directory.listFiles( (dir, name) -> name.endsWith(".game") ); for (File file : files) { file.delete(); } } protected void parseScenario(String filename) throws FileNotFoundException { parserState = ParserState.INIT; File f = new File(filename); try(Scanner scanner = new Scanner(f)) { while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); if (line == null || line.isEmpty() || line.startsWith("#")) { continue; } if (line.startsWith("$include")) { includeFrom(line); continue; } if (line.startsWith("$expected")) { parserState = ParserState.EXPECTED; continue; } parseLine(line); } } } private void parseLine(String line) { if (parserState == ParserState.EXPECTED) { expectedResults.add(line); // just remember for future use return; } Matcher m = pattern.matcher(line); if (m.matches()) { String zone = m.group(1); String nickname = m.group(2); if (nickname.equals("ComputerA") || nickname.equals("ComputerB")) { List<Card> cards = null; List<PermanentCard> perms = null; Zone gameZone; if ("hand".equalsIgnoreCase(zone)) { gameZone = Zone.HAND; cards = nickname.equals("ComputerA") ? handCardsA : handCardsB; } else if ("battlefield".equalsIgnoreCase(zone)) { gameZone = Zone.BATTLEFIELD; perms = nickname.equals("ComputerA") ? battlefieldCardsA : battlefieldCardsB; } else if ("graveyard".equalsIgnoreCase(zone)) { gameZone = Zone.GRAVEYARD; cards = nickname.equals("ComputerA") ? graveyardCardsA : graveyardCardsB; } else if ("library".equalsIgnoreCase(zone)) { gameZone = Zone.LIBRARY; cards = nickname.equals("ComputerA") ? libraryCardsA : libraryCardsB; } else if ("player".equalsIgnoreCase(zone)) { String command = m.group(3); if ("life".equals(command)) { if (nickname.equals("ComputerA")) { commandsA.put(Zone.OUTSIDE, "life:" + m.group(4)); } else { commandsB.put(Zone.OUTSIDE, "life:" + m.group(4)); } } return; } else { return; // go parse next line } String cardName = m.group(3); Integer amount = Integer.parseInt(m.group(4)); boolean tapped = m.group(5) != null && m.group(5).equals(":{tapped}"); if (cardName.equals("clear")) { if (nickname.equals("ComputerA")) { commandsA.put(gameZone, "clear"); } else { commandsB.put(gameZone, "clear"); } } else { for (int i = 0; i < amount; i++) { CardInfo cardInfo = CardRepository.instance.findCard(cardName); Card card = cardInfo != null ? cardInfo.getCard() : null; if (card != null) { if (gameZone == Zone.BATTLEFIELD) { PermanentCard p = new PermanentCard(card, null, currentGame); p.setTapped(tapped); perms.add(p); } else { cards.add(card); } } else { logger.fatal("Couldn't find a card: " + cardName); logger.fatal("line: " + line); } } } } else { logger.warn("Unknown player: " + nickname); } } else { logger.warn("Init string wasn't parsed: " + line); } } private void includeFrom(String line) throws FileNotFoundException { String[] params = line.split(" "); if (params.length == 2) { String paramName = params[1]; if (!paramName.contains("..")) { String includePath = TESTS_PATH + paramName; File f = new File(includePath); if (f.exists()) { parseScenario(includePath); } else { logger.warn("Ignored (file doesn't exist): " + line); } } else { logger.warn("Ignored (wrong charactres): " + line); } } else { logger.warn("Ignored (wrong size): " + line); } } protected Player createPlayer(String name, PlayerType playerType) { Optional<Player> playerOptional = PlayerFactory.instance.createPlayer(playerType, name, RangeOfInfluence.ALL, 5); return playerOptional.orElseThrow(() -> new NullPointerException("PlayerFactory error - player is not created")); } protected Player createRandomPlayer(String name) { return new RandomPlayer(name); } }