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);
}
}