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 d9fb5f50cb..36c55ce246 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 @@ -126,7 +126,7 @@ public class ComputerPlayer> extends PlayerImpl i @Override public boolean chooseMulligan(Game game) { logger.debug("chooseMulligan"); - if (hand.size() < 6) + if (hand.size() < 6 || isTestMode()) return false; Set lands = hand.getCards(new FilterLandCard(), game); if (lands.size() < 2 || lands.size() > hand.size() - 2) diff --git a/Mage.Server/plugins/mage-player-ai-ma.jar b/Mage.Server/plugins/mage-player-ai-ma.jar index 457fe25f12..c448c463f4 100644 Binary files a/Mage.Server/plugins/mage-player-ai-ma.jar and b/Mage.Server/plugins/mage-player-ai-ma.jar differ diff --git a/Mage.Server/plugins/mage-player-ai.jar b/Mage.Server/plugins/mage-player-ai.jar index 75031eae3b..4d95d5cbb2 100644 Binary files a/Mage.Server/plugins/mage-player-ai.jar and b/Mage.Server/plugins/mage-player-ai.jar differ diff --git a/Mage.Server/plugins/mage-player-aiminimax.jar b/Mage.Server/plugins/mage-player-aiminimax.jar index 7e6aedbce4..7aaa7fc459 100644 Binary files a/Mage.Server/plugins/mage-player-aiminimax.jar and b/Mage.Server/plugins/mage-player-aiminimax.jar differ diff --git a/Mage.Server/plugins/mage-player-human.jar b/Mage.Server/plugins/mage-player-human.jar index 6546beb288..cf067be229 100644 Binary files a/Mage.Server/plugins/mage-player-human.jar and b/Mage.Server/plugins/mage-player-human.jar differ diff --git a/Mage.Tests/plugins/mage-player-ai-ma.jar b/Mage.Tests/plugins/mage-player-ai-ma.jar index 849a57c7a3..c448c463f4 100644 Binary files a/Mage.Tests/plugins/mage-player-ai-ma.jar and b/Mage.Tests/plugins/mage-player-ai-ma.jar differ diff --git a/Mage.Tests/plugins/mage-player-aiminimax.jar b/Mage.Tests/plugins/mage-player-aiminimax.jar index 7e6aedbce4..7aaa7fc459 100644 Binary files a/Mage.Tests/plugins/mage-player-aiminimax.jar and b/Mage.Tests/plugins/mage-player-aiminimax.jar differ diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java index 4f1fcfb776..9cf3cf72c2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java @@ -5,6 +5,7 @@ import mage.cards.Card; import mage.cards.decks.Deck; import mage.game.Game; import mage.game.GameException; +import mage.game.GameOptions; import mage.game.TwoPlayerDuel; import mage.game.permanent.PermanentCard; import mage.players.Player; @@ -23,18 +24,6 @@ import java.util.regex.Matcher; */ public class PlayGameTest extends MageTestBase { - private List handCardsA = new ArrayList(); - private List handCardsB = new ArrayList(); - private List battlefieldCardsA = new ArrayList(); - private List battlefieldCardsB = new ArrayList(); - private List graveyardCardsA = new ArrayList(); - private List graveyardCardsB = new ArrayList(); - private List libraryCardsA = new ArrayList(); - private List libraryCardsB = new ArrayList(); - - private Map commandsA = new HashMap(); - private Map commandsB = new HashMap(); - @Test public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { Game game = new TwoPlayerDuel(Constants.MultiplayerAttackOption.LEFT, Constants.RangeOfInfluence.ALL); @@ -68,7 +57,9 @@ public class PlayGameTest extends MageTestBase { boolean testMode = true; long t1 = System.nanoTime(); - game.start(computerA.getId(), testMode); + GameOptions options = new GameOptions(); + options.testMode = true; + game.start(computerA.getId(), options); long t2 = System.nanoTime(); logger.info("Winner: " + game.getWinner()); @@ -77,100 +68,4 @@ public class PlayGameTest extends MageTestBase { throw new RuntimeException("Lost :("); }*/ } - - private void addCard(List cards, String name, int count) { - for (int i = 0; i < count; i++) { - Card card = Sets.findCard(name, true); - if (card == null) { - throw new IllegalArgumentException("Couldn't find a card for test: " + name); - } - cards.add(card); - } - } - - private void parseScenario(String filename) throws FileNotFoundException { - File f = new File(filename); - Scanner scanner = new Scanner(f); - try { - while (scanner.hasNextLine()) { - String line = scanner.nextLine().trim(); - if (line == null || line.isEmpty() || line.startsWith("#")) continue; - 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 cards = null; - List perms = null; - Constants.Zone gameZone; - if ("hand".equalsIgnoreCase(zone)) { - gameZone = Constants.Zone.HAND; - cards = nickname.equals("ComputerA") ? handCardsA : handCardsB; - } else if ("battlefield".equalsIgnoreCase(zone)) { - gameZone = Constants.Zone.BATTLEFIELD; - perms = nickname.equals("ComputerA") ? battlefieldCardsA : battlefieldCardsB; - } else if ("graveyard".equalsIgnoreCase(zone)) { - gameZone = Constants.Zone.GRAVEYARD; - cards = nickname.equals("ComputerA") ? graveyardCardsA : graveyardCardsB; - } else if ("library".equalsIgnoreCase(zone)) { - gameZone = Constants.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(Constants.Zone.OUTSIDE, "life:" + m.group(4)); - } else { - commandsB.put(Constants.Zone.OUTSIDE, "life:" + m.group(4)); - } - } - continue; - } else { - continue; // 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++) { - Card card = Sets.findCard(cardName, true); - if (card != null) { - if (gameZone.equals(Constants.Zone.BATTLEFIELD)) { - PermanentCard p = new PermanentCard(card, null); - 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); - } - } - } finally { - scanner.close(); - } - } - - private Player createPlayer(String name, String playerType) { - return PlayerFactory.getInstance().createPlayer(playerType, name, Constants.RangeOfInfluence.ALL); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java new file mode 100644 index 0000000000..de598db5b0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java @@ -0,0 +1,143 @@ +package org.mage.test.serverside.base; + +import mage.Constants; +import mage.filter.Filter; +import mage.players.Player; + +/** + * Interface for all test initialization and assertion operations. + */ +public interface CardTestAPI { + + /** + * Types of game result. + */ + public enum GameResult { + WON, + LOST, + DRAW + } + + //******* INITIALIZATION METHODS *******/ + + /** + * Default game initialization params for red player (that plays with Mountains) + */ + void useRedDefault(); + + /** + * Removes all cards from player's library from the game. + * Usually this should be used once before initialization to form the library in certain order. + * + * @param player {@link Player} to remove all library cards from. + */ + void removeAllCardsFromLibrary(Player player); + + /** + * Add a card to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + */ + void addCard(Constants.Zone gameZone, Player player, String cardName); + + /** + * Add any amount of cards to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + * @param count Amount of cards to be added. + */ + void addCard(Constants.Zone gameZone, Player player, String cardName, int count); + + /** + * Add any amount of cards to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + * @param count Amount of cards to be added. + * @param tapped In case gameZone is Battlefield, determines whether permanent should be tapped. + * In case gameZone is other than Battlefield, {@link IllegalArgumentException} is thrown + */ + void addCard(Constants.Zone gameZone, Player player, String cardName, int count, boolean tapped); + + /** + * Set player's initial life count. + * + * @param player {@link Player} to set life count for. + * @param life Life count to set. + */ + void setLife(Player player, int life); + + //******* GAME OPTIONS *******/ + + /** + * Define turn number to stop the game on. + */ + void setStopOnTurn(int turn); + + //******* ASSERT METHODS *******/ + + /** + * Assert turn number after test execution. + * + * @param turn Expected turn number to compare with. + */ + void assertTurn(int turn) throws AssertionError; + + /** + * Assert game result for the player after test execution. + * + * @param player {@link Player} to get game result for. + * @param result Expected {@link GameResult} to compare with. + */ + void assertResult(Player player, GameResult result) throws AssertionError; + + /** + * Assert player's life count after test execution. + * + * @param player {@link Player} to get life for comparison. + * @param life Expected player's life to compare with. + */ + void assertLife(Player player, int life) throws AssertionError; + + /** + * Assert creature's power and toughness by card name. + *

+ * Throws {@link AssertionError} in the following cases: + * 1. no such player + * 2. no such creature under player's control + * 3. depending on comparison scope: + * 3a. any: no creature under player's control with the specified p\t params + * 3b. all: there is at least one creature with the cardName with the different p\t params + * + * @param player {@link Player} to get creatures for comparison. + * @param cardName Card name to compare with. + * @param power Expected power to compare with. + * @param toughness Expected toughness to compare with. + * @param scope {@link Filter.ComparisonScope} Use ANY, if you want "at least one creature with given name should have specified p\t" + * Use ALL, if you want "all creature with gived name should have specified p\t" + */ + void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) + throws AssertionError; + + /** + * Assert permanent count under player's control. + * + * @param player {@link Player} which permanents should be counted. + * @param count Expected count. + */ + void assertPermanentCount(Player player, int count) throws AssertionError; + + /** + * Assert permanent count under player's control. + * + * @param player {@link Player} which permanents should be counted. + * @param cardName Name of the cards that should be counted. + * @param count Expected count. + */ + void assertPermanentCount(Player player, String cardName, int count) throws AssertionError; +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestBase.java new file mode 100644 index 0000000000..07ccb98b63 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestBase.java @@ -0,0 +1,335 @@ +package org.mage.test.serverside.base; + +import mage.Constants; +import mage.cards.Card; +import mage.cards.decks.Deck; +import mage.filter.Filter; +import mage.game.Game; +import mage.game.GameException; +import mage.game.GameOptions; +import mage.game.TwoPlayerDuel; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.sets.Sets; +import org.junit.Assert; +import org.junit.Before; +import org.mage.test.serverside.base.impl.CardTestAPIImpl; + +import java.io.File; +import java.io.FileNotFoundException; + +/** + * Base class for testing single cards and effects. + * + * @author ayratn + */ +public abstract class CardTestBase extends CardTestAPIImpl { + + protected enum AIType { + MinimaxHybrid, + MAD + } + + protected enum ExpectedType { + TURN_NUMBER, + RESULT, + LIFE, + BATTLEFIELD, + GRAVEYARD, + UNKNOWN + } + + /** + * Computer types used to test cards. + * By default: MAD. + */ + private AIType aiTypeA, aiTypeB; + + public CardTestBase() { + aiTypeA = CardTestBase.AIType.MAD; + aiTypeB = CardTestBase.AIType.MAD; + } + + public CardTestBase(AIType aiTypeA, AIType aiTypeB) { + this.aiTypeA = aiTypeA; + this.aiTypeB = aiTypeB; + } + + @Before + public void reset() throws GameException, FileNotFoundException { + if (currentGame != null) { + logger.info("Resetting previous game and creating new one!"); + currentGame = null; + System.gc(); + } + + Game game = new TwoPlayerDuel(Constants.MultiplayerAttackOption.LEFT, Constants.RangeOfInfluence.ALL); + + computerA = aiTypeA.equals(CardTestBase.AIType.MinimaxHybrid) ? + createPlayer("ComputerA", "Computer - minimax hybrid") : + createPlayer("ComputerA", "Computer - mad"); + computerA.setTestMode(true); + Deck deck = Deck.load(Sets.loadDeck("RB Aggro.dck")); + if (deck.getCards().size() < 40) { + throw new IllegalArgumentException("Couldn't load deck, deck size=" + deck.getCards().size()); + } + game.addPlayer(computerA, deck); + game.loadCards(deck.getCards(), computerA.getId()); + + computerB = aiTypeB.equals(CardTestBase.AIType.MinimaxHybrid) ? + createPlayer("ComputerB", "Computer - minimax hybrid") : + createPlayer("ComputerB", "Computer - mad"); + computerB.setTestMode(true); + Deck deck2 = Deck.load(Sets.loadDeck("RB Aggro.dck")); + if (deck2.getCards().size() < 40) { + throw new IllegalArgumentException("Couldn't load deck, deck size=" + deck2.getCards().size()); + } + game.addPlayer(computerB, deck2); + game.loadCards(deck2.getCards(), computerB.getId()); + activePlayer = computerA; + currentGame = game; + + stopOnTurn = null; + handCardsA.clear(); + handCardsB.clear(); + battlefieldCardsA.clear(); + battlefieldCardsB.clear(); + graveyardCardsA.clear(); + graveyardCardsB.clear(); + libraryCardsA.clear(); + libraryCardsB.clear(); + commandsA.clear(); + commandsB.clear(); + } + + public void load(String path) throws FileNotFoundException, GameException { + load(path, AIType.MAD, AIType.MAD); + } + + public void load(String path, AIType aiTypeA, AIType aiTypeB) throws FileNotFoundException, GameException { + String cardPath = TESTS_PATH + path; + File checkFile = new File(cardPath); + if (!checkFile.exists()) { + throw new FileNotFoundException("Couldn't find test file: " + cardPath); + } + if (checkFile.isDirectory()) { + throw new FileNotFoundException("Couldn't find test file: " + cardPath + ". It is directory."); + } + + if (currentGame != null) { + logger.info("Resetting previous game and creating new one!"); + currentGame = null; + System.gc(); + } + + Game game = new TwoPlayerDuel(Constants.MultiplayerAttackOption.LEFT, Constants.RangeOfInfluence.ALL); + + computerA = aiTypeA.equals(CardTestBase.AIType.MinimaxHybrid) ? + createPlayer("ComputerA", "Computer - minimax hybrid") : + createPlayer("ComputerA", "Computer - mad"); + computerA.setTestMode(true); + + Deck deck = Deck.load(Sets.loadDeck("RB Aggro.dck")); + + if (deck.getCards().size() < 40) { + throw new IllegalArgumentException("Couldn't load deck, deck size=" + deck.getCards().size()); + } + game.addPlayer(computerA, deck); + game.loadCards(deck.getCards(), computerA.getId()); + + computerB = aiTypeB.equals(CardTestBase.AIType.MinimaxHybrid) ? + createPlayer("ComputerB", "Computer - minimax hybrid") : + createPlayer("ComputerB", "Computer - mad"); + computerB.setTestMode(true); + Deck deck2 = Deck.load(Sets.loadDeck("RB Aggro.dck")); + if (deck2.getCards().size() < 40) { + throw new IllegalArgumentException("Couldn't load deck, deck size=" + deck2.getCards().size()); + } + game.addPlayer(computerB, deck2); + game.loadCards(deck2.getCards(), computerB.getId()); + + parseScenario(cardPath); + + activePlayer = computerA; + currentGame = game; + } + + /** + * Starts testing card by starting current game. + * + * @throws IllegalStateException In case game wasn't created previously. Use {@link #load} method to initialize the game. + */ + public void execute() throws IllegalStateException { + if (currentGame == null || activePlayer == null) { + throw new IllegalStateException("Game is not initialized. Use load method to load a test case and initialize a game."); + } + + currentGame.cheat(computerA.getId(), commandsA); + currentGame.cheat(computerA.getId(), libraryCardsA, handCardsA, battlefieldCardsA, graveyardCardsA); + currentGame.cheat(computerB.getId(), commandsB); + currentGame.cheat(computerB.getId(), libraryCardsB, handCardsB, battlefieldCardsB, graveyardCardsB); + + boolean testMode = true; + long t1 = System.nanoTime(); + GameOptions gameOptions = new GameOptions(); + gameOptions.testMode = true; + gameOptions.stopOnTurn = stopOnTurn; + currentGame.start(activePlayer.getId(), gameOptions); + long t2 = System.nanoTime(); + logger.info("Winner: " + currentGame.getWinner()); + logger.info("Time: " + (t2 - t1) / 1000000 + " ms"); + + assertTheResults(); + } + + /** + * Assert expected and actual results. + */ + private void assertTheResults() { + logger.info("Matching expected results:"); + for (String line : expectedResults) { + boolean ok = false; + try { + ExpectedType type = getExpectedType(line); + if (type.equals(CardTestBase.ExpectedType.UNKNOWN)) { + throw new AssertionError("Unknown expected type, check the line in $expected section=" + line); + } + parseType(type, line); + ok = true; + } finally { + logger.info(" " + line + " - " + (ok ? "OK" : "ERROR")); + } + } + } + + private ExpectedType getExpectedType(String line) { + if (line.startsWith("turn:")) { + return CardTestBase.ExpectedType.TURN_NUMBER; + } + if (line.startsWith("result:")) { + return CardTestBase.ExpectedType.RESULT; + } + if (line.startsWith("life:")) { + return CardTestBase.ExpectedType.LIFE; + } + if (line.startsWith("battlefield:")) { + return CardTestBase.ExpectedType.BATTLEFIELD; + } + if (line.startsWith("graveyard:")) { + return CardTestBase.ExpectedType.GRAVEYARD; + } + return CardTestBase.ExpectedType.UNKNOWN; + } + + private void parseType(ExpectedType type, String line) { + if (type.equals(CardTestBase.ExpectedType.TURN_NUMBER)) { + int turn = getIntParam(line, 1); + Assert.assertEquals("Turn numbers are not equal", turn, currentGame.getTurnNum()); + return; + } + if (type.equals(CardTestBase.ExpectedType.RESULT)) { + String expected = getStringParam(line, 1); + String actual = "draw"; + if (currentGame.getWinner().equals("Player ComputerA is the winner")) { + actual = "won"; + } else if (currentGame.getWinner().equals("Player ComputerB is the winner")) { + actual = "lost"; + } + Assert.assertEquals("Game results are not equal", expected, actual); + return; + } + if (type.equals(CardTestBase.ExpectedType.LIFE)) { + String player = getStringParam(line, 1); + int expected = getIntParam(line, 2); + if (player.equals("ComputerA")) { + int actual = currentGame.getPlayer(computerA.getId()).getLife(); + Assert.assertEquals("Life amounts are not equal", expected, actual); + } else if (player.equals("ComputerB")) { + int actual = currentGame.getPlayer(computerB.getId()).getLife(); + Assert.assertEquals("Life amounts are not equal", expected, actual); + } else { + throw new IllegalArgumentException("Wrong player in 'life' line, player=" + player + ", line=" + line); + } + return; + } + if (type.equals(CardTestBase.ExpectedType.BATTLEFIELD)) { + String playerName = getStringParam(line, 1); + String cardName = getStringParam(line, 2); + int expectedCount = getIntParam(line, 3); + Player player = null; + if (playerName.equals("ComputerA")) { + player = currentGame.getPlayer(computerA.getId()); + } else if (playerName.equals("ComputerB")) { + player = currentGame.getPlayer(computerB.getId()); + } else { + throw new IllegalArgumentException("Wrong player in 'battlefield' line, player=" + player + ", line=" + line); + } + int actualCount = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllPermanents()) { + if (permanent.getControllerId().equals(player.getId())) { + if (permanent.getName().equals(cardName)) { + actualCount++; + } + } + } + Assert.assertEquals("(Battlefield) Card counts are not equal (" + cardName + ")", expectedCount, actualCount); + return; + } + if (type.equals(CardTestBase.ExpectedType.GRAVEYARD)) { + String playerName = getStringParam(line, 1); + String cardName = getStringParam(line, 2); + int expectedCount = getIntParam(line, 3); + Player player = null; + if (playerName.equals("ComputerA")) { + player = currentGame.getPlayer(computerA.getId()); + } else if (playerName.equals("ComputerB")) { + player = currentGame.getPlayer(computerB.getId()); + } else { + throw new IllegalArgumentException("Wrong player in 'graveyard' line, player=" + player + ", line=" + line); + } + int actualCount = 0; + for (Card card : player.getGraveyard().getCards(currentGame)) { + if (card.getName().equals(cardName)) { + actualCount++; + } + } + Assert.assertEquals("(Graveyard) Card counts are not equal (" + cardName + ")", expectedCount, actualCount); + return; + } + } + + private int getIntParam(String line, int index) { + String[] params = line.split(":"); + if (index > params.length - 1) { + throw new IllegalArgumentException("Not correct line: " + line); + } + return Integer.parseInt(params[index]); + } + + private String getStringParam(String line, int index) { + String[] params = line.split(":"); + if (index > params.length - 1) { + throw new IllegalArgumentException("Not correct line: " + line); + } + return params[index]; + } + + protected void checkPermanentPT(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) { + if (currentGame == null) { + throw new IllegalStateException("Current game is null"); + } + if (scope.equals(Filter.ComparisonScope.All)) { + throw new UnsupportedOperationException("ComparisonScope.All is not implemented."); + } + int count = 0; + int fit = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { + if (permanent.getName().equals(cardName)) { + Assert.assertEquals("Power is not the same", power, permanent.getPower().getValue()); + Assert.assertEquals("Toughness is not the same", toughness, permanent.getToughness().getValue()); + break; + } + } + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index 03518fd762..00d5c7c183 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java @@ -1,7 +1,12 @@ package org.mage.test.serverside.base; +import mage.Constants; +import mage.cards.Card; +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.server.game.DeckValidatorFactory; import mage.server.game.GameFactory; import mage.server.game.PlayerFactory; @@ -10,19 +15,25 @@ import mage.server.util.ConfigSettings; import mage.server.util.PluginClassLoader; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; +import mage.sets.Sets; import mage.util.Copier; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import org.junit.BeforeClass; import java.io.File; +import java.io.FileNotFoundException; import java.io.FilenameFilter; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; +import java.util.*; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** + * Base class for all tests. + * * @author ayratn */ -public class MageTestBase { +public abstract class MageTestBase { protected static Logger logger = Logger.getLogger(MageTestBase.class); public static PluginClassLoader classLoader = new PluginClassLoader(); @@ -31,6 +42,58 @@ public class MageTestBase { protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)(:\\{tapped\\})?"); + protected List handCardsA = new ArrayList(); + protected List handCardsB = new ArrayList(); + protected List battlefieldCardsA = new ArrayList(); + protected List battlefieldCardsB = new ArrayList(); + protected List graveyardCardsA = new ArrayList(); + protected List graveyardCardsB = new ArrayList(); + protected List libraryCardsA = new ArrayList(); + protected List libraryCardsB = new ArrayList(); + + protected Map commandsA = new HashMap(); + protected Map commandsB = new HashMap(); + + protected Player computerA; + protected Player computerB; + + /** + * 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 enum ParserState { + INIT, + OPTIONS, + EXPECTED + } + + protected ParserState parserState; + + /** + * Expected results of the test. + * Read from test case in {@link String} based format: + *

+ * Example: + * turn:1 + * result:won:ComputerA + * life:ComputerA:20 + * life:ComputerB:0 + * battlefield:ComputerB:Tine Shrike:0 + * graveyard:ComputerB:Tine Shrike:1 + */ + protected List expectedResults = new ArrayList(); + + protected static final String TESTS_PATH = "tests" + File.separator; + @BeforeClass public static void init() { Logger.getRootLogger().setLevel(Level.DEBUG); @@ -108,4 +171,128 @@ public class MageTestBase { file.delete(); } } + + protected void parseScenario(String filename) throws FileNotFoundException { + parserState = ParserState.INIT; + File f = new File(filename); + Scanner scanner = new Scanner(f); + try { + 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); + } + } finally { + scanner.close(); + } + } + + private void parseLine(String line) { + if (parserState.equals(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 cards = null; + List perms = null; + Constants.Zone gameZone; + if ("hand".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.HAND; + cards = nickname.equals("ComputerA") ? handCardsA : handCardsB; + } else if ("battlefield".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.BATTLEFIELD; + perms = nickname.equals("ComputerA") ? battlefieldCardsA : battlefieldCardsB; + } else if ("graveyard".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.GRAVEYARD; + cards = nickname.equals("ComputerA") ? graveyardCardsA : graveyardCardsB; + } else if ("library".equalsIgnoreCase(zone)) { + gameZone = Constants.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(Constants.Zone.OUTSIDE, "life:" + m.group(4)); + } else { + commandsB.put(Constants.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++) { + Card card = Sets.findCard(cardName, true); + if (card != null) { + if (gameZone.equals(Constants.Zone.BATTLEFIELD)) { + PermanentCard p = new PermanentCard(card, null); + 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, String playerType) { + return PlayerFactory.getInstance().createPlayer(playerType, name, Constants.RangeOfInfluence.ALL); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestAPIImpl.java new file mode 100644 index 0000000000..e84866d601 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestAPIImpl.java @@ -0,0 +1,296 @@ +package org.mage.test.serverside.base.impl; + +import mage.Constants; +import mage.cards.Card; +import mage.filter.Filter; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.players.Player; +import mage.sets.Sets; +import org.junit.Assert; +import org.mage.test.serverside.base.CardTestAPI; +import org.mage.test.serverside.base.MageTestBase; + +import java.util.List; + +/** + * API for test initialization and asserting the test results. + * + * @author ayratn + */ +public abstract class CardTestAPIImpl extends MageTestBase implements CardTestAPI { + + /** + * Default game initialization params for red player (that plays with Mountains) + */ + public void useRedDefault() { + // *** ComputerA *** + // battlefield:ComputerA:Mountain:5 + addCard(Constants.Zone.BATTLEFIELD, computerA, "Mountain", 5); + // hand:ComputerA:Mountain:4 + addCard(Constants.Zone.HAND, computerA, "Mountain", 5); + // library:ComputerA:clear:0 + removeAllCardsFromLibrary(computerA); + // library:ComputerA:Mountain:10 + addCard(Constants.Zone.LIBRARY, computerA, "Mountain", 10); + + // *** ComputerB *** + // battlefield:ComputerB:Plains:2 + addCard(Constants.Zone.BATTLEFIELD, computerB, "Plains", 2); + // hand:ComputerB:Plains:2 + addCard(Constants.Zone.HAND, computerB, "Plains", 2); + // library:ComputerB:clear:0 + removeAllCardsFromLibrary(computerB); + // library:ComputerB:Plains:10 + addCard(Constants.Zone.LIBRARY, computerB, "Plains", 10); + } + + /** + * Removes all cards from player's library from the game. + * Usually this should be used once before initialization to form the library in certain order. + * + * @param player {@link Player} to remove all library cards from. + */ + public void removeAllCardsFromLibrary(Player player) { + if (player.equals(computerA)) { + commandsA.put(Constants.Zone.LIBRARY, "clear"); + } else if (player.equals(computerB)) { + commandsB.put(Constants.Zone.LIBRARY, "clear"); + } + } + + /** + * Add a card to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + */ + public void addCard(Constants.Zone gameZone, Player player, String cardName) { + addCard(gameZone, player, cardName, 1, false); + } + + /** + * Add any amount of cards to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + * @param count Amount of cards to be added. + */ + public void addCard(Constants.Zone gameZone, Player player, String cardName, int count) { + addCard(gameZone, player, cardName, count, false); + } + + /** + * Add any amount of cards to specified zone of specified player. + * + * @param gameZone {@link Constants.Zone} to add cards to. + * @param player {@link Player} to add cards for. Use either computerA or computerB. + * @param cardName Card name in string format. + * @param count Amount of cards to be added. + * @param tapped In case gameZone is Battlefield, determines whether permanent should be tapped. + * In case gameZone is other than Battlefield, {@link IllegalArgumentException} is thrown + */ + public void addCard(Constants.Zone gameZone, Player player, String cardName, int count, boolean tapped) { + + + if (gameZone.equals(Constants.Zone.BATTLEFIELD)) { + for (int i = 0; i < count; i++) { + Card card = Sets.findCard(cardName, true); + PermanentCard p = new PermanentCard(card, null); + p.setTapped(tapped); + if (player.equals(computerA)) { + battlefieldCardsA.add(p); + } else if (player.equals(computerB)) { + battlefieldCardsB.add(p); + } + } + } else { + if (tapped) { + throw new IllegalArgumentException("Parameter tapped=true can be used only for Zone.BATTLEFIELD."); + } + List cards = getCardList(gameZone, player); + for (int i = 0; i < count; i++) { + Card card = Sets.findCard(cardName, true); + cards.add(card); + } + } + } + + /** + * Returns card list containter for specified game zone and player. + * + * @param gameZone + * @param player + * @return + */ + private List getCardList(Constants.Zone gameZone, Player player) { + if (player.equals(computerA)) { + if (gameZone.equals(Constants.Zone.HAND)) { + return handCardsA; + } else if (gameZone.equals(Constants.Zone.GRAVEYARD)) { + return graveyardCardsA; + } else if (gameZone.equals(Constants.Zone.LIBRARY)) { + return libraryCardsA; + } + } else if (player.equals(computerB)) { + if (gameZone.equals(Constants.Zone.HAND)) { + return handCardsB; + } else if (gameZone.equals(Constants.Zone.GRAVEYARD)) { + return graveyardCardsB; + } else if (gameZone.equals(Constants.Zone.LIBRARY)) { + return libraryCardsB; + } + } + return null; + } + + /** + * Set player's initial life count. + * + * @param player {@link Player} to set life count for. + * @param life Life count to set. + */ + public void setLife(Player player, int life) { + if (player.equals(computerA)) { + commandsA.put(Constants.Zone.OUTSIDE, "life:" + String.valueOf(life)); + } else if (player.equals(computerB)) { + commandsB.put(Constants.Zone.OUTSIDE, "life:" + String.valueOf(life)); + } + } + + /** + * Define turn number to stop the game on. + */ + public void setStopOnTurn(int turn) { + stopOnTurn = turn == -1 ? null : Integer.valueOf(turn); + } + + /** + * Assert turn number after test execution. + * + * @param turn Expected turn number to compare with. 1-based. + */ + public void assertTurn(int turn) throws AssertionError { + Assert.assertEquals("Turn numbers are not equal", turn, currentGame.getTurnNum()); + } + + /** + * Assert game result after test execution. + * + * @param result Expected {@link GameResult} to compare with. + */ + public void assertResult(Player player, GameResult result) throws AssertionError { + if (player.equals(computerA)) { + GameResult actual = CardTestAPI.GameResult.DRAW; + if (currentGame.getWinner().equals("Player ComputerA is the winner")) { + actual = CardTestAPI.GameResult.WON; + } else if (currentGame.getWinner().equals("Player ComputerB is the winner")) { + actual = CardTestAPI.GameResult.LOST; + } + Assert.assertEquals("Game results are not equal", result, actual); + } else if (player.equals(computerB)) { + GameResult actual = CardTestAPI.GameResult.DRAW; + if (currentGame.getWinner().equals("Player ComputerB is the winner")) { + actual = CardTestAPI.GameResult.WON; + } else if (currentGame.getWinner().equals("Player ComputerA is the winner")) { + actual = CardTestAPI.GameResult.LOST; + } + Assert.assertEquals("Game results are not equal", result, actual); + } + } + + /** + * Assert player's life count after test execution. + * + * @param player {@link Player} to get life for comparison. + * @param life Expected player's life to compare with. + */ + public void assertLife(Player player, int life) throws AssertionError { + int actual = currentGame.getPlayer(player.getId()).getLife(); + Assert.assertEquals("Life amounts are not equal", life, actual); + } + + /** + * Assert creature's power and toughness by card name. + *

+ * Throws {@link AssertionError} in the following cases: + * 1. no such player + * 2. no such creature under player's control + * 3. depending on comparison scope: + * 3a. any: no creature under player's control with the specified p\t params + * 3b. all: there is at least one creature with the cardName with the different p\t params + * + * @param player {@link Player} to get creatures for comparison. + * @param cardName Card name to compare with. + * @param power Expected power to compare with. + * @param toughness Expected toughness to compare with. + * @param scope {@link mage.filter.Filter.ComparisonScope} Use ANY, if you want "at least one creature with given name should have specified p\t" + * Use ALL, if you want "all creature with gived name should have specified p\t" + */ + public void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) + throws AssertionError { + int count = 0; + int fit = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { + if (permanent.getName().equals(cardName)) { + count++; + if (scope.equals(Filter.ComparisonScope.All)) { + Assert.assertEquals("Power is not the same (" + power + " vs. " + permanent.getPower().getValue() + ")", + power, permanent.getPower().getValue()); + Assert.assertEquals("Toughness is not the same (" + toughness + " vs. " + permanent.getToughness().getValue() + ")", + toughness, permanent.getToughness().getValue()); + } else if (scope.equals(Filter.ComparisonScope.Any)) { + if (power == permanent.getPower().getValue() && toughness == permanent.getToughness().getValue()) { + fit++; + break; + } + } + } + } + + Assert.assertTrue("There is no such permanent under player's control, player=" + player.getName() + + ", cardName=" + cardName, count > 0); + + if (scope.equals(Filter.ComparisonScope.Any)) { + Assert.assertTrue("There is no such creature under player's control with specified power&toughness, player=" + player.getName() + + ", cardName=" + cardName, fit > 0); + } + } + + /** + * Assert permanent count under player's control. + * + * @param player {@link Player} which permanents should be counted. + * @param count Expected count. + */ + public void assertPermanentCount(Player player, int count) throws AssertionError { + int actualCount = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllPermanents()) { + if (permanent.getControllerId().equals(player.getId())) { + actualCount++; + } + } + Assert.assertEquals("(Battlefield) Card counts are not equal ", count, actualCount); + } + + /** + * Assert permanent count under player's control. + * + * @param player {@link Player} which permanents should be counted. + * @param cardName Name of the cards that should be counted. + * @param count Expected count. + */ + public void assertPermanentCount(Player player, String cardName, int count) throws AssertionError { + int actualCount = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllPermanents()) { + if (permanent.getControllerId().equals(player.getId())) { + if (permanent.getName().equals(cardName)) { + actualCount++; + } + } + } + Assert.assertEquals("(Battlefield) Card counts are not equal (" + cardName + ")", count, actualCount); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/cards/effects/BoostContinuousEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/cards/effects/BoostContinuousEffectTest.java new file mode 100644 index 0000000000..fc1948a013 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/cards/effects/BoostContinuousEffectTest.java @@ -0,0 +1,23 @@ +package org.mage.test.serverside.cards.effects; + +import com.sun.xml.bind.v2.schemagen.xmlschema.Any; +import mage.filter.Filter; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestBase; + +/** + * Tests continuous boost effect like "White creatures you control get +1/+1". + * + * @author ayratn + */ +public class BoostContinuousEffectTest extends CardTestBase { + + @Test + public void testHonorOfThePoor() throws Exception { + load("M11/Honor of the Pure.test"); + execute(); + + checkPermanentPT(computerA, "Tine Shrike", 3, 2, Filter.ComparisonScope.Any); + checkPermanentPT(computerA, "Runeclaw Bear", 2, 2, Filter.ComparisonScope.Any); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/cards/single/mbs/BurntheImpureTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/cards/single/mbs/BurntheImpureTest.java new file mode 100644 index 0000000000..46d5c9022e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/cards/single/mbs/BurntheImpureTest.java @@ -0,0 +1,74 @@ +package org.mage.test.serverside.cards.single.mbs; + +import mage.Constants; +import mage.filter.Filter; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestAPI; +import org.mage.test.serverside.base.CardTestBase; + +/** + * First JUnit tests for Mage card. + * + * @ayratn + */ +public class BurntheImpureTest extends CardTestBase { + + /** + * Reproduces the test written in MBS/Burn the Impure.test + * + * Actually it can be tested with one java line that loads all test metadata from text file: + * load("MBS/Burn the Impure.test"); + * + * But it was decided to use java code only. + * + * @throws Exception + */ + @Test + public void testVersusInfectCreature() throws Exception { + // $include red.default + useRedDefault(); + // hand:ComputerA:Burn the Impure:1 + addCard(Constants.Zone.HAND, computerA, "Burn the Impure"); + // battlefield:ComputerB:Tine Shrike:1 + addCard(Constants.Zone.BATTLEFIELD, computerB, "Tine Shrike"); + // player:ComputerB:life:3 + setLife(computerB, 3); + + setStopOnTurn(2); + execute(); + + // turn:1 + assertTurn(1); + // result:won + assertResult(computerA, CardTestAPI.GameResult.WON); + // life:ComputerA:20 + assertLife(computerA, 20); + // life:ComputerB:0 + assertLife(computerB, 0); + // assert Tine Shrike has been killed + assertPermanentCount(computerB, "Tine Shrike", 0); + } + + /** + * load("MBS/Burn the Impure - no infect.test"); + * @throws Exception + */ + @Test + public void testVersusNonInfectCreature() throws Exception { + useRedDefault(); + addCard(Constants.Zone.HAND, computerA, "Burn the Impure"); + addCard(Constants.Zone.BATTLEFIELD, computerB, "Runeclaw Bear", 3); + setLife(computerB, 3); + + setStopOnTurn(2); + execute(); + + assertTurn(2); + assertResult(computerA, CardTestAPI.GameResult.DRAW); + assertLife(computerA, 20); + assertLife(computerB, 3); + assertPermanentCount(computerB, "Runeclaw Bear", 2); + assertPowerToughness(computerB, "Runeclaw Bear", 2, 2, Filter.ComparisonScope.Any); + assertPowerToughness(computerB, "Runeclaw Bear", 2, 2, Filter.ComparisonScope.All); + } +} diff --git a/Mage.Tests/tests/M11/Honor of the Pure.test b/Mage.Tests/tests/M11/Honor of the Pure.test new file mode 100644 index 0000000000..17ee83f0f3 --- /dev/null +++ b/Mage.Tests/tests/M11/Honor of the Pure.test @@ -0,0 +1,29 @@ +### Test playing Honor of the Pure ### +####################################### + +$include green.white.default + +### ComputerA ### +# Hand +hand:ComputerA:Honor of the Pure:1 +# Battlefield +battlefield:ComputerB:Tine Shrike:1 +battlefield:ComputerB:Runeclaw Bear:1 + + +### ComputerB ### +# nothing + +$options + +exit_on_turn:2 + +$expected + +turn:1 +result:draw +life:ComputerA:20 +life:ComputerB:20 +battlefield:ComputerA:Tine Shrike:1 +battlefield:ComputerA:Runeclaw Bear:1 +# note: abilities are tested in code \ No newline at end of file diff --git a/Mage.Tests/tests/MBS/Burn the Impure - no infect.test b/Mage.Tests/tests/MBS/Burn the Impure - no infect.test new file mode 100644 index 0000000000..c32bd845e6 --- /dev/null +++ b/Mage.Tests/tests/MBS/Burn the Impure - no infect.test @@ -0,0 +1,30 @@ +### Test playing Burn the Impure ### +### Target: creature with no Infect ### +####################################### + +$include red.default + +### ComputerA ### +# Hand +hand:ComputerA:Burn the Impure:1 + +### ComputerB ### +# Battlefield +battlefield:ComputerB:Runeclaw Bear:1 +# Life +player:ComputerB:life:3 + +$options + +exit_on_turn:2 + +$expected + +turn:1 +result:draw +life:ComputerA:20 +life:ComputerB:3 +#battlefield:ComputerB:Runeclaw Bear:0 +#graveyard:ComputerB:Runeclaw Bear:1 + + diff --git a/Mage.Tests/tests/MBS/Burn the Impure.test b/Mage.Tests/tests/MBS/Burn the Impure.test new file mode 100644 index 0000000000..2b72110fa1 --- /dev/null +++ b/Mage.Tests/tests/MBS/Burn the Impure.test @@ -0,0 +1,29 @@ +### Test playing Burn the Impure ### +### Target: creature with Infect ### +####################################### + +$include red.default + +### ComputerA ### +# Hand +hand:ComputerA:Burn the Impure:1 + +### ComputerB ### +# Battlefield +battlefield:ComputerB:Tine Shrike:1 +# Life +player:ComputerB:life:3 + +$options + +exit_on_turn:2 + +$expected + +turn:1 +result:won +life:ComputerA:20 +life:ComputerB:0 +#battlefield:ComputerB:Tine Shrike:0 +#graveyard:ComputerB:Tine Shrike:1 + diff --git a/Mage.Tests/tests/green.white.default b/Mage.Tests/tests/green.white.default new file mode 100644 index 0000000000..fb76ec3502 --- /dev/null +++ b/Mage.Tests/tests/green.white.default @@ -0,0 +1,31 @@ +# Default init game state for white player (that plays with Plains) + +### ComputerA ### + +# Battlefield +battlefield:ComputerA:Plains:5 +battlefield:ComputerA:Forest:5 + +# Hand +hand:ComputerA:Plains:2 +hand:ComputerA:Forest:2 + +# Library +# from down to top +library:ComputerA:clear:0 +library:ComputerA:Plains:5 +library:ComputerA:Forest:5 + +### ComputerB ### + +# Battlefield +battlefield:ComputerB:Mountain:2 + +# Hand +hand:ComputerB:Mountain:2 + +# Library +# from down to top +library:ComputerB:clear:0 +library:ComputerB:Mountain:10 + diff --git a/Mage.Tests/tests/red.default b/Mage.Tests/tests/red.default new file mode 100644 index 0000000000..4f2a2f72a9 --- /dev/null +++ b/Mage.Tests/tests/red.default @@ -0,0 +1,28 @@ +# Default init game state for red player (that plays with Mountains) + +### ComputerA ### + +# Battlefield +battlefield:ComputerA:Mountain:5 + +# Hand +hand:ComputerA:Mountain:4 + +# Library +# from down to top +library:ComputerA:clear:0 +library:ComputerA:Mountain:10 + +### ComputerB ### + +# Battlefield +battlefield:ComputerB:Plains:2 + +# Hand +hand:ComputerB:Plains:2 + +# Library +# from down to top +library:ComputerB:clear:0 +library:ComputerB:Plains:10 + diff --git a/Mage.Tests/tests/white.default b/Mage.Tests/tests/white.default new file mode 100644 index 0000000000..44e51196b2 --- /dev/null +++ b/Mage.Tests/tests/white.default @@ -0,0 +1,28 @@ +# Default init game state for white player (that plays with Plains) + +### ComputerA ### + +# Battlefield +battlefield:ComputerA:Plains:5 + +# Hand +hand:ComputerA:Plains:4 + +# Library +# from down to top +library:ComputerA:clear:0 +library:ComputerA:Plains:10 + +### ComputerB ### + +# Battlefield +battlefield:ComputerB:Mountain:2 + +# Hand +hand:ComputerB:Mountain:2 + +# Library +# from down to top +library:ComputerB:clear:0 +library:ComputerB:Mountain:10 + diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 147e876934..0b03427c5d 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -139,7 +139,7 @@ public interface Game extends MageItem, Serializable { //game play methods //public void init(UUID choosingPlayerId); public void start(UUID choosingPlayerId); - public void start(UUID choosingPlayerId, boolean testMode); + public void start(UUID choosingPlayerId, GameOptions options); public void end(); public void mulligan(UUID playerId); public void quit(UUID playerId); diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 90559d77d6..45d011f96a 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -292,19 +292,20 @@ public abstract class GameImpl> implements Game, Serializa @Override public void start(UUID choosingPlayerId) { - start(choosingPlayerId, false); + start(choosingPlayerId, GameOptions.getDefault()); } @Override - public void start(UUID choosingPlayerId, boolean testMode) { - init(choosingPlayerId, testMode); + public void start(UUID choosingPlayerId, GameOptions options) { + init(choosingPlayerId, options.testMode); PlayerList players = state.getPlayerList(startingPlayerId); Player player = getPlayer(players.get()); while (!isGameOver()) { - if (player.getId().equals(startingPlayerId)) { + //if (player.getId().equals(startingPlayerId)) { state.setTurnNum(state.getTurnNum() + 1); fireInformEvent("Turn " + Integer.toString(state.getTurnNum())); - } + //} + if (checkStopOnTurnOption(options)) return; state.setActivePlayerId(player.getId()); state.getTurn().play(this, player.getId()); if (isGameOver()) @@ -318,6 +319,17 @@ public abstract class GameImpl> implements Game, Serializa saveState(); } + private boolean checkStopOnTurnOption(GameOptions options) { + if (options.stopOnTurn != null) { + if (options.stopOnTurn.equals(state.getTurnNum())) { + winnerId = null; //DRAW + saveState(); + return true; + } + } + return false; + } + protected void init(UUID choosingPlayerId, boolean testMode) { for (Player player: state.getPlayers().values()) { player.beginTurn(this); diff --git a/Mage/src/mage/game/GameOptions.java b/Mage/src/mage/game/GameOptions.java new file mode 100644 index 0000000000..c4fe621afa --- /dev/null +++ b/Mage/src/mage/game/GameOptions.java @@ -0,0 +1,27 @@ +package mage.game; + +/** + * Game options for Mage game. + * Mainly used in tests to configure {@link GameImpl} with specific params. + * + * @author ayratn + */ +public class GameOptions { + + private static GameOptions defInstance = new GameOptions(); + + public static GameOptions getDefault() { + return defInstance; + } + + /** + * Defines the running mode. There are some exclusions made for test mode. + */ + public boolean testMode = false; + + /** + * Defines the turn number game should stop on. + * By default, is null meaning that game shouldn't stop on any specific turn. + */ + public Integer stopOnTurn = null; +} diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 3cf0a99349..3519e39791 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -96,6 +96,9 @@ public interface Player extends MageItem, Copyable { public boolean hasLeft(); public ManaPool getManaPool(); public Set getInRange(); + + public boolean isTestMode(); + public void setTestMode(boolean value); public void init(Game game); public void init(Game game, boolean testMode); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 0821edb05a..22f955ded4 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -98,6 +98,7 @@ public abstract class PlayerImpl> implements Player, Ser protected boolean left; protected RangeOfInfluence range; protected Set inRange = new HashSet(); + protected boolean isTestMode = false; @Override public abstract T copy(); @@ -1013,4 +1014,11 @@ public abstract class PlayerImpl> implements Player, Ser } } + public boolean isTestMode() { + return isTestMode; + } + + public void setTestMode(boolean value) { + this.isTestMode = value; + } }