From fabcc28e46c9d2369ec72cf753860ceddbd203ef Mon Sep 17 00:00:00 2001 From: BetaSteward Date: Sun, 13 Feb 2011 08:20:14 -0500 Subject: [PATCH 1/8] more draft/sealed fixes --- .../client/dialog/NewTournamentDialog.java | 44 ++++++++++++- .../client/table/TournamentPlayerPanel.java | 5 +- .../src/mage/player/human/HumanPlayer.java | 5 +- .../target/maven-archiver/pom.properties | 5 -- Mage.Server/plugins/mage-player-ai.jar | Bin 39830 -> 40088 bytes Mage.Server/plugins/mage-player-human.jar | Bin 11637 -> 11629 bytes .../java/mage/server/TableController.java | 62 +++++------------- .../main/java/mage/server/TableManager.java | 18 ++++- .../tournament/TournamentController.java | 14 ++-- Mage/src/mage/game/Table.java | 24 +++---- Mage/src/mage/game/events/TableEvent.java | 8 ++- .../mage/game/events/TableEventSource.java | 4 +- Mage/src/mage/game/match/Match.java | 7 ++ Mage/src/mage/game/match/MatchImpl.java | 46 +++++++++++++ .../mage/game/tournament/TournamentImpl.java | 12 ++-- Mage/src/mage/players/Player.java | 3 +- 16 files changed, 171 insertions(+), 86 deletions(-) delete mode 100644 Mage.Server.Plugins/Mage.Tournament.BoosterDraft/target/maven-archiver/pom.properties diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index ae7e3f079bc..56be4033ac8 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -276,7 +276,7 @@ public class NewTournamentDialog extends MageDialog { tOptions.setTournamentType(tournamentType.getName()); tOptions.getPlayerTypes().add("Human"); for (TournamentPlayerPanel player: players) { - tOptions.getPlayerTypes().add(player.getPlayerType()); + tOptions.getPlayerTypes().add((String) player.getPlayerType().getSelectedItem()); } if (tournamentType.isDraft()) { DraftOptions options = new DraftOptions(); @@ -347,12 +347,33 @@ public class NewTournamentDialog extends MageDialog { pack.setModel(new DefaultComboBoxModel(Sets.getInstance().values().toArray())); pnlPacks.add(pack); packs.add(pack); + pack.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + packActionPerformed(evt); + } + }); } this.pack(); this.revalidate(); this.repaint(); } + private void packActionPerformed(java.awt.event.ActionEvent evt) { + boolean start = false; + int selectedIndex = 0; + for (JComboBox pack: packs) { + if (!start) { + if (evt.getSource().equals(pack)) { + start = true; + selectedIndex = pack.getSelectedIndex(); + } + } + else { + pack.setSelectedIndex(selectedIndex); + } + } + } + private void createPlayers(int numPlayers) { if (numPlayers > players.size()) { while (players.size() != numPlayers) { @@ -373,12 +394,33 @@ public class NewTournamentDialog extends MageDialog { this.pnlOtherPlayers.removeAll(); for (TournamentPlayerPanel panel: players) { this.pnlOtherPlayers.add(panel); + panel.getPlayerType().addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + playerActionPerformed(evt); + } + }); } this.pack(); this.revalidate(); this.repaint(); } + private void playerActionPerformed(java.awt.event.ActionEvent evt) { + boolean start = false; + int selectedIndex = 0; + for (TournamentPlayerPanel player: players) { + if (!start) { + if (evt.getSource().equals(player.getPlayerType())) { + start = true; + selectedIndex = player.getPlayerType().getSelectedIndex(); + } + } + else { + player.getPlayerType().setSelectedIndex(selectedIndex); + } + } + } + public TableView getTable() { return table; } diff --git a/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java b/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java index d530406bdbf..466b08bd093 100644 --- a/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java @@ -36,6 +36,7 @@ package mage.client.table; import java.util.UUID; import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; import mage.client.MageFrame; import mage.client.remote.Session; @@ -59,8 +60,8 @@ public class TournamentPlayerPanel extends javax.swing.JPanel { this.lblPlayerNum.setText("Player " + playerNum); } - public String getPlayerType() { - return (String) this.cbPlayerType.getSelectedItem(); + public JComboBox getPlayerType() { + return this.cbPlayerType; } public boolean joinTournamentTable(UUID roomId, UUID tableId) { 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 3c1d67d5bc1..9754d58fbe8 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 @@ -57,6 +57,7 @@ import mage.choices.ChoiceImpl; import mage.filter.common.FilterCreatureForCombat; import mage.game.Game; import mage.game.Table; +import mage.game.match.Match; import mage.game.permanent.Permanent; import mage.game.tournament.Tournament; import mage.target.Target; @@ -482,8 +483,8 @@ public class HumanPlayer extends PlayerImpl { } @Override - public void sideboard(Table table, Deck deck) { - table.fireSideboardEvent(playerId, deck); + public void sideboard(Match match, Deck deck) { + match.fireSideboardEvent(playerId, deck); } @Override diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/target/maven-archiver/pom.properties b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/target/maven-archiver/pom.properties deleted file mode 100644 index 2e014c18ee0..00000000000 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/target/maven-archiver/pom.properties +++ /dev/null @@ -1,5 +0,0 @@ -#Generated by Maven -#Sat Feb 12 11:24:53 MSK 2011 -version=0.6 -groupId=org.mage -artifactId=Mage-Tournament-BoosterDraft diff --git a/Mage.Server/plugins/mage-player-ai.jar b/Mage.Server/plugins/mage-player-ai.jar index 0d84511d774eedc33a5a2c94f94eaf59e9a29aec..5bdf67b3a46f76b6d17256c548a4be797b5ebd8b 100644 GIT binary patch delta 20181 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 delta 8757 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 sessionPlayerMap = new ConcurrentHashMap(); public TableController(UUID sessionId, MatchOptions options) { @@ -108,24 +104,19 @@ public class TableController { public TableController(UUID sessionId, TournamentOptions options) { this.sessionId = sessionId; chatId = ChatManager.getInstance().createChatSession(); - this.tournamentOptions = options; tournament = TournamentFactory.getInstance().createTournament(options.getTournamentType(), options); table = new Table(options.getTournamentType(), options.getName(), DeckValidatorFactory.getInstance().createDeckValidator(options.getMatchOptions().getDeckType()), options.getPlayerTypes(), true); - init(); } private void init() { - table.addTableEventListener( + match.addTableEventListener( new Listener () { @Override public void event(TableEvent event) { switch (event.getEventType()) { case SIDEBOARD: - sideboard(event.getPlayerId(), event.getDeck()); + sideboard(event.getPlayerId(), event.getDeck(), event.getTimeout()); break; -// case CONSTRUCT: -// construct(event.getPlayerId(), event.getDeck()); -// break; case SUBMIT_DECK: submitDeck(event.getPlayerId(), event.getDeck()); break; @@ -209,8 +200,7 @@ public class TableController { private void submitDeck(UUID sessionId, Deck deck) { if (table.getState() == TableState.SIDEBOARDING) { - MatchPlayer player = match.getPlayer(sessionPlayerMap.get(sessionId)); - player.submitDeck(deck); + match.submitDeck(sessionPlayerMap.get(sessionId), deck); } else { TournamentManager.getInstance().submitDeck(tournament.getId(), sessionId, deck); @@ -262,7 +252,13 @@ public class TableController { } public synchronized void startMatch(UUID sessionId) { - if (sessionId.equals(this.sessionId) && table.getState() == TableState.STARTING) { + if (sessionId.equals(this.sessionId)) { + startMatch(); + } + } + + public synchronized void startMatch() { + if (table.getState() == TableState.STARTING) { try { match.startMatch(); startGame(null); @@ -284,7 +280,6 @@ public class TableController { public synchronized void startTournament(UUID sessionId) { if (sessionId.equals(this.sessionId) && table.getState() == TableState.STARTING) { - table.initTournament(); TournamentManager.getInstance().createTournamentSession(tournament, sessionPlayerMap, table.getId()); SessionManager sessionManager = SessionManager.getInstance(); for (Entry entry: sessionPlayerMap.entrySet()) { @@ -302,42 +297,19 @@ public class TableController { } } - private void sideboard() { - table.sideboard(); - for (MatchPlayer player: match.getPlayers()) { - player.setSideboarding(); - player.getPlayer().sideboard(table, player.getDeck()); - } - while (!match.isDoneSideboarding()){} - } - - private void sideboard(UUID playerId, Deck deck) { + private void sideboard(UUID playerId, Deck deck, int timeout) { SessionManager sessionManager = SessionManager.getInstance(); for (Entry entry: sessionPlayerMap.entrySet()) { if (entry.getValue().equals(playerId)) { - sessionManager.getSession(entry.getKey()).sideboard(deck, table.getId(), SIDEBOARD_TIME); + sessionManager.getSession(entry.getKey()).sideboard(deck, table.getId(), timeout); break; } } } -// public void construct() { -// table.construct(); -// for (TournamentPlayer player: tournament.getPlayers()) { -// player.setConstructing(); -// player.getPlayer().construct(table, player.getDeck()); -// } -// } - -// private void construct(UUID playerId, Deck deck) { -// SessionManager sessionManager = SessionManager.getInstance(); -// for (Entry entry: sessionPlayerMap.entrySet()) { -// if (entry.getValue().equals(playerId)) { -// sessionManager.getSession(entry.getKey()).construct(deck, table.getId(), CONSTRUCT_TIME); -// break; -// } -// } -// } + public void construct() { + table.construct(); + } public void endGame() { UUID choosingPlayerId = match.getChooser(); @@ -347,7 +319,8 @@ public class TableController { GameManager.getInstance().removeGame(match.getGame().getId()); try { if (!match.isMatchOver()) { - sideboard(); + table.sideboard(); + match.sideboard(); startGame(choosingPlayerId); } } catch (GameException ex) { @@ -433,4 +406,5 @@ public class TableController { public Match getMatch() { return match; } + } diff --git a/Mage.Server/src/main/java/mage/server/TableManager.java b/Mage.Server/src/main/java/mage/server/TableManager.java index 2a120a9a9ef..914c77039b6 100644 --- a/Mage.Server/src/main/java/mage/server/TableManager.java +++ b/Mage.Server/src/main/java/mage/server/TableManager.java @@ -67,6 +67,13 @@ public class TableManager { return tableController.getTable(); } + public Table createTable(MatchOptions options) { + TableController tableController = new TableController(UUID.randomUUID(), options); + controllers.put(tableController.getTable().getId(), tableController); + tables.put(tableController.getTable().getId(), tableController.getTable()); + return tableController.getTable(); + } + public Table createTournamentTable(UUID sessionId, TournamentOptions options) { TableController tableController = new TableController(sessionId, options); controllers.put(tableController.getTable().getId(), tableController); @@ -127,6 +134,10 @@ public class TableManager { controllers.get(tableId).startMatch(sessionId); } + public void startMatch(UUID roomId, UUID tableId) { + controllers.get(tableId).startMatch(); + } + public void startTournament(UUID sessionId, UUID roomId, UUID tableId) { controllers.get(tableId).startTournament(sessionId); } @@ -161,11 +172,12 @@ public class TableManager { } } -// public void construct(UUID tableId) { -// controllers.get(tableId).construct(); -// } + public void construct(UUID tableId) { + controllers.get(tableId).construct(); + } public void addPlayer(UUID sessionId, UUID tableId, Player player, String playerType, Deck deck) throws GameException { controllers.get(tableId).addPlayer(sessionId, player, playerType, deck); } + } diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java index 36ce1e3230d..9c18fefe560 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java @@ -66,8 +66,6 @@ public class TournamentController { private ConcurrentHashMap sessionPlayerMap = new ConcurrentHashMap(); private ConcurrentHashMap tournamentSessions = new ConcurrentHashMap(); - private static final int CONSTRUCT_TIME = 600; - public TournamentController(Tournament tournament, ConcurrentHashMap sessionPlayerMap, UUID tableId) { sessionId = UUID.randomUUID(); this.sessionPlayerMap = sessionPlayerMap; @@ -96,6 +94,9 @@ public class TournamentController { case SUBMIT_DECK: submitDeck(event.getPlayerId(), event.getDeck()); break; + case CONSTRUCT: + construct(); + break; } } } @@ -170,12 +171,12 @@ public class TournamentController { private void startMatch(TournamentPairing pair, MatchOptions matchOptions) { try { TableManager tableManager = TableManager.getInstance(); - Table table = tableManager.createTable(sessionId, matchOptions); + Table table = tableManager.createTable(matchOptions); TournamentPlayer player1 = pair.getPlayer1(); TournamentPlayer player2 = pair.getPlayer2(); tableManager.addPlayer(getPlayerSessionId(player1.getPlayer().getId()), table.getId(), player1.getPlayer(), player1.getPlayerType(), player1.getDeck()); tableManager.addPlayer(getPlayerSessionId(player2.getPlayer().getId()), table.getId(), player2.getPlayer(), player2.getPlayerType(), player2.getDeck()); - tableManager.startMatch(sessionId, null, table.getId()); + tableManager.startMatch(null, table.getId()); pair.setMatch(tableManager.getMatch(table.getId())); } catch (GameException ex) { Logger.getLogger(TournamentController.class.getName()).log(Level.SEVERE, null, ex); @@ -186,12 +187,15 @@ public class TournamentController { TableManager.getInstance().startDraft(tableId, draft); } + private void construct() { + TableManager.getInstance().construct(tableId); + } + private synchronized void construct(UUID sessionId, Deck deck, int timeout) { if (tournamentSessions.containsKey(sessionId)) tournamentSessions.get(sessionId).construct(deck, timeout); } - public void submitDeck(UUID sessionId, Deck deck) { tournamentSessions.get(sessionPlayerMap.get(sessionId)).submitDeck(deck); } diff --git a/Mage/src/mage/game/Table.java b/Mage/src/mage/game/Table.java index 93bf40f2a4a..8a244c67d69 100644 --- a/Mage/src/mage/game/Table.java +++ b/Mage/src/mage/game/Table.java @@ -84,10 +84,18 @@ public class Table implements Serializable { state = TableState.DUELING; } + public void initTournament() { + state = TableState.DUELING; + } + public void initDraft() { state = TableState.DRAFTING; } + public void construct() { + state = TableState.CONSTRUCTING; + } + public void endGame() { state = TableState.FINISHED; } @@ -157,24 +165,8 @@ public class Table implements Serializable { return this.name; } - public void fireSideboardEvent(UUID playerId, Deck deck) { - tableEventSource.fireTableEvent(EventType.SIDEBOARD, playerId, deck); - } - - public void fireConstructEvent(UUID playerId, Deck deck) { - tableEventSource.fireTableEvent(EventType.CONSTRUCT, playerId, deck); - } - - public void fireSubmitDeckEvent(UUID playerId, Deck deck) { - tableEventSource.fireTableEvent(EventType.SUBMIT_DECK, playerId, deck); - } - public void addTableEventListener(Listener listener) { tableEventSource.addListener(listener); } - public void initTournament() { - state = TableState.CONSTRUCTING; - } - } diff --git a/Mage/src/mage/game/events/TableEvent.java b/Mage/src/mage/game/events/TableEvent.java index 4d17710e19f..aea35cadeb8 100644 --- a/Mage/src/mage/game/events/TableEvent.java +++ b/Mage/src/mage/game/events/TableEvent.java @@ -56,6 +56,7 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab private Deck deck; private TournamentPairing pair; private MatchOptions options; + private int timeout; public TableEvent(EventType eventType) { super(eventType); @@ -70,11 +71,12 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab this.eventType = eventType; } - public TableEvent(EventType eventType, UUID playerId, Deck deck) { + public TableEvent(EventType eventType, UUID playerId, Deck deck, int timeout) { super(playerId); this.playerId = playerId; this.deck = deck; this.eventType = eventType; + this.timeout = timeout; } public TableEvent(EventType eventType, String message, Draft draft) { @@ -126,4 +128,8 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab public MatchOptions getMatchOptions() { return options; } + + public int getTimeout() { + return timeout; + } } diff --git a/Mage/src/mage/game/events/TableEventSource.java b/Mage/src/mage/game/events/TableEventSource.java index acd66a3184a..bfb1bf00010 100644 --- a/Mage/src/mage/game/events/TableEventSource.java +++ b/Mage/src/mage/game/events/TableEventSource.java @@ -67,8 +67,8 @@ public class TableEventSource implements EventSource, Serializable { dispatcher.fireEvent(new TableEvent(eventType, message, cards, game)); } - public void fireTableEvent(EventType eventType, UUID playerId, Deck deck) { - dispatcher.fireEvent(new TableEvent(eventType, playerId, deck)); + public void fireTableEvent(EventType eventType, UUID playerId, Deck deck, int timeout) { + dispatcher.fireEvent(new TableEvent(eventType, playerId, deck, timeout)); } public void fireTableEvent(EventType eventType, TournamentPairing pair, MatchOptions options) { diff --git a/Mage/src/mage/game/match/Match.java b/Mage/src/mage/game/match/Match.java index 032edac9ea2..64ae80b3439 100644 --- a/Mage/src/mage/game/match/Match.java +++ b/Mage/src/mage/game/match/Match.java @@ -33,6 +33,8 @@ import java.util.UUID; import mage.cards.decks.Deck; import mage.game.Game; import mage.game.GameException; +import mage.game.events.Listener; +import mage.game.events.TableEvent; import mage.players.Player; /** @@ -46,8 +48,10 @@ public interface Match { public List getPlayers(); public MatchPlayer getPlayer(UUID playerId); public void addPlayer(Player player, Deck deck); + public void submitDeck(UUID playerId, Deck deck); public void startMatch() throws GameException; public void startGame() throws GameException; + public void sideboard(); public void endGame(); public Game getGame(); public List getGames(); @@ -56,4 +60,7 @@ public interface Match { public boolean isDoneSideboarding(); public UUID getChooser(); + public void addTableEventListener(Listener listener); + public void fireSideboardEvent(UUID playerId, Deck deck); + } diff --git a/Mage/src/mage/game/match/MatchImpl.java b/Mage/src/mage/game/match/MatchImpl.java index 01c3988e481..c6b5370a78f 100644 --- a/Mage/src/mage/game/match/MatchImpl.java +++ b/Mage/src/mage/game/match/MatchImpl.java @@ -35,6 +35,10 @@ import java.util.logging.Logger; import mage.cards.decks.Deck; import mage.game.Game; import mage.game.GameException; +import mage.game.events.Listener; +import mage.game.events.TableEvent; +import mage.game.events.TableEvent.EventType; +import mage.game.events.TableEventSource; import mage.players.Player; import mage.util.Logging; @@ -45,11 +49,14 @@ import mage.util.Logging; public abstract class MatchImpl implements Match { private final static Logger logger = Logging.getLogger(MatchImpl.class.getName()); + private static final int SIDEBOARD_TIME = 180; protected UUID id = UUID.randomUUID(); protected List players = new ArrayList(); protected List games = new ArrayList(); protected MatchOptions options; + + protected TableEventSource tableEventSource = new TableEventSource(); public MatchImpl(MatchOptions options) { this.options = options; @@ -151,6 +158,26 @@ public abstract class MatchImpl implements Match { return loserId; } + @Override + public void addTableEventListener(Listener listener) { + tableEventSource.addListener(listener); + } + + @Override + public void sideboard() { + for (MatchPlayer player: this.players) { + player.setSideboarding(); + player.getPlayer().sideboard(this, player.getDeck()); + } + synchronized(this) { + while (!isDoneSideboarding()) { + try { + this.wait(); + } catch (InterruptedException ex) { } + } + } + } + @Override public boolean isDoneSideboarding() { for (MatchPlayer player: this.players) { @@ -160,4 +187,23 @@ public abstract class MatchImpl implements Match { return true; } + @Override + public void fireSideboardEvent(UUID playerId, Deck deck) { + MatchPlayer player = getPlayer(playerId); + if (player != null) { + tableEventSource.fireTableEvent(EventType.SIDEBOARD, playerId, deck, SIDEBOARD_TIME); + } + } + + @Override + public void submitDeck(UUID playerId, Deck deck) { + MatchPlayer player = getPlayer(playerId); + if (player != null) { + player.submitDeck(deck); + } + synchronized (this) { + this.notifyAll(); + } + } + } diff --git a/Mage/src/mage/game/tournament/TournamentImpl.java b/Mage/src/mage/game/tournament/TournamentImpl.java index 979d247627c..fd75633b11b 100644 --- a/Mage/src/mage/game/tournament/TournamentImpl.java +++ b/Mage/src/mage/game/tournament/TournamentImpl.java @@ -41,6 +41,7 @@ import java.util.logging.Logger; import mage.cards.Card; import mage.cards.ExpansionSet; import mage.cards.decks.Deck; +import mage.game.Table; import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; import mage.game.events.PlayerQueryEventSource; @@ -63,8 +64,10 @@ public abstract class TournamentImpl implements Tournament { protected String matchName; protected TournamentOptions options; - protected transient TableEventSource tableEventSource = new TableEventSource(); - protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource(); + protected TableEventSource tableEventSource = new TableEventSource(); + protected PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource(); + + private static final int CONSTRUCT_TIME = 600; public TournamentImpl(TournamentOptions options) { this.options = options; @@ -209,7 +212,7 @@ public abstract class TournamentImpl implements Tournament { @Override public void fireSubmitDeckEvent(UUID playerId, Deck deck) { - tableEventSource.fireTableEvent(EventType.SUBMIT_DECK, playerId, deck); + tableEventSource.fireTableEvent(EventType.SUBMIT_DECK, playerId, deck, 0); } @Override @@ -220,10 +223,11 @@ public abstract class TournamentImpl implements Tournament { @Override public void fireConstructEvent(UUID playerId, Deck deck) { TournamentPlayer player = players.get(playerId); - playerQueryEventSource.construct(playerId, "Construct", deck, 600); + playerQueryEventSource.construct(playerId, "Construct", deck, CONSTRUCT_TIME); } public void construct() { + tableEventSource.fireTableEvent(EventType.CONSTRUCT); for (TournamentPlayer player: players.values()) { player.setConstructing(); player.getPlayer().construct(this, player.getDeck()); diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 850752feac0..9749716a47c 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -56,6 +56,7 @@ import mage.game.events.GameEvent; import mage.game.Game; import mage.game.Table; import mage.game.draft.Draft; +import mage.game.match.Match; import mage.game.permanent.Permanent; import mage.game.tournament.Tournament; import mage.target.Target; @@ -154,7 +155,7 @@ public interface Player extends MageItem, Copyable { public abstract void selectBlockers(Game game); public abstract void assignDamage(int damage, List targets, UUID sourceId, Game game); public abstract int getAmount(int min, int max, String message, Game game); - public abstract void sideboard(Table table, Deck deck); + public abstract void sideboard(Match match, Deck deck); public abstract void construct(Tournament tournament, Deck deck); public abstract void pickCard(List cards, Deck deck, Draft draft); From 9e7c79f3e71b5c85b24ae8164984c1f0e7fecd52 Mon Sep 17 00:00:00 2001 From: BetaSteward Date: Sun, 13 Feb 2011 08:21:59 -0500 Subject: [PATCH 2/8] missed a file --- .../src/main/java/mage/player/ai/ComputerPlayer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 358572660f5..e0eb3d3540f 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 @@ -77,6 +77,7 @@ import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.Table; import mage.game.combat.CombatGroup; +import mage.game.match.Match; import mage.game.permanent.Permanent; import mage.game.tournament.Tournament; import mage.player.ai.utils.RateCard; @@ -795,9 +796,9 @@ public class ComputerPlayer> extends PlayerImpl i } @Override - public void sideboard(Table table, Deck deck) { + public void sideboard(Match match, Deck deck) { //TODO: improve this - table.fireSubmitDeckEvent(playerId, deck); + match.submitDeck(playerId, deck); } @Override From 121a920c85740b58c65746b78bf5f9663c89ad13 Mon Sep 17 00:00:00 2001 From: BetaSteward Date: Sun, 13 Feb 2011 08:22:35 -0500 Subject: [PATCH 3/8] fixed AI swallowing exceptions --- .../src/mage/player/ai/ComputerPlayer2.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java index a3c3008bb02..5a30fd91840 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java @@ -304,9 +304,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements logger.fine("simulating - timed out"); task.cancel(true); } catch (ExecutionException e) { - + logger.log(Level.SEVERE, "Simulation error", e); + task.cancel(true); } catch (InterruptedException e) { - + logger.log(Level.SEVERE, "Simulation interrupted", e); + task.cancel(true); } } From 127b7a95622333d7860006bfcedfd0c75e4abb9b Mon Sep 17 00:00:00 2001 From: Loki Date: Sun, 13 Feb 2011 23:17:27 +0200 Subject: [PATCH 4/8] ARB --- .../sets/alarareborn/CapturedSunlight.java | 62 ++++++++++++++ .../mage/sets/alarareborn/MagefireWings.java | 82 +++++++++++++++++++ .../sets/alarareborn/VithianRenegades.java | 80 ++++++++++++++++++ .../src/mage/sets/planechase/BullCerodon.java | 54 ++++++++++++ .../mage/sets/shardsofalara/BullCerodon.java | 66 +++++++++++++++ .../shardsofalara/JhessianInfiltrator.java | 67 +++++++++++++++ 6 files changed, 411 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/alarareborn/CapturedSunlight.java create mode 100644 Mage.Sets/src/mage/sets/alarareborn/MagefireWings.java create mode 100644 Mage.Sets/src/mage/sets/alarareborn/VithianRenegades.java create mode 100644 Mage.Sets/src/mage/sets/planechase/BullCerodon.java create mode 100644 Mage.Sets/src/mage/sets/shardsofalara/BullCerodon.java create mode 100644 Mage.Sets/src/mage/sets/shardsofalara/JhessianInfiltrator.java diff --git a/Mage.Sets/src/mage/sets/alarareborn/CapturedSunlight.java b/Mage.Sets/src/mage/sets/alarareborn/CapturedSunlight.java new file mode 100644 index 00000000000..705e5eae196 --- /dev/null +++ b/Mage.Sets/src/mage/sets/alarareborn/CapturedSunlight.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.alarareborn; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.CascadeAbility; +import mage.cards.CardImpl; + +/** + * + * @author Loki + */ +public class CapturedSunlight extends CardImpl { + + public CapturedSunlight (UUID ownerId) { + super(ownerId, 66, "Captured Sunlight", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{2}{G}{W}"); + this.expansionSetCode = "ARB"; + this.color.setGreen(true); + this.color.setWhite(true); + this.getSpellAbility().addEffect(new GainLifeEffect(4)); + this.addAbility(new CascadeAbility()); + } + + public CapturedSunlight (final CapturedSunlight card) { + super(card); + } + + @Override + public CapturedSunlight copy() { + return new CapturedSunlight(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/alarareborn/MagefireWings.java b/Mage.Sets/src/mage/sets/alarareborn/MagefireWings.java new file mode 100644 index 00000000000..ff6efa0593e --- /dev/null +++ b/Mage.Sets/src/mage/sets/alarareborn/MagefireWings.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.alarareborn; + +import java.util.UUID; + +import mage.Constants; +import mage.Constants.CardType; +import mage.Constants.Duration; +import mage.Constants.Rarity; +import mage.Constants.Zone; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continious.BoostEnchantedEffect; +import mage.abilities.effects.common.continious.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Loki + */ +public class MagefireWings extends CardImpl { + + public MagefireWings (UUID ownerId) { + super(ownerId, 88, "Magefire Wings", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{U}{R}"); + this.expansionSetCode = "ARB"; + this.subtype.add("Aura"); + this.color.setBlue(true); + this.color.setRed(true); + + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Constants.Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(2, 0, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(FlyingAbility.getInstance(), Constants.AttachmentType.AURA))); + } + + public MagefireWings (final MagefireWings card) { + super(card); + } + + @Override + public MagefireWings copy() { + return new MagefireWings(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/alarareborn/VithianRenegades.java b/Mage.Sets/src/mage/sets/alarareborn/VithianRenegades.java new file mode 100644 index 00000000000..4c621a774e9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/alarareborn/VithianRenegades.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.alarareborn; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Duration; +import mage.Constants.Rarity; +import mage.Constants.Zone; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.filter.Filter; +import mage.filter.FilterPermanent; +import mage.target.TargetPermanent; + +/** + * + * @author Loki + */ +public class VithianRenegades extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("artifact"); + + static { + filter.getCardType().add(CardType.ARTIFACT); + filter.setScopeCardType(Filter.ComparisonScope.Any); + } + + public VithianRenegades (UUID ownerId) { + super(ownerId, 64, "Vithian Renegades", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + this.expansionSetCode = "ARB"; + this.subtype.add("Human"); + this.subtype.add("Shaman"); + this.color.setRed(true); + this.color.setGreen(true); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + public VithianRenegades (final VithianRenegades card) { + super(card); + } + + @Override + public VithianRenegades copy() { + return new VithianRenegades(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/planechase/BullCerodon.java b/Mage.Sets/src/mage/sets/planechase/BullCerodon.java new file mode 100644 index 00000000000..b6de07d254a --- /dev/null +++ b/Mage.Sets/src/mage/sets/planechase/BullCerodon.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.planechase; + +import java.util.UUID; + +/** + * + * @author Loki + */ +public class BullCerodon extends mage.sets.shardsofalara.BullCerodon { + + public BullCerodon (UUID ownerId) { + super(ownerId); + this.cardNumber = 84; + this.expansionSetCode = "HOP"; + } + + public BullCerodon (final BullCerodon card) { + super(card); + } + + @Override + public BullCerodon copy() { + return new BullCerodon(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/shardsofalara/BullCerodon.java b/Mage.Sets/src/mage/sets/shardsofalara/BullCerodon.java new file mode 100644 index 00000000000..639cb0bb824 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shardsofalara/BullCerodon.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.shardsofalara; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Rarity; +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; + +/** + * + * @author Loki + */ +public class BullCerodon extends CardImpl { + + public BullCerodon (UUID ownerId) { + super(ownerId, 161, "Bull Cerodon", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{4}{R}{W}"); + this.expansionSetCode = "ALA"; + this.subtype.add("Beast"); + this.color.setRed(true); + this.color.setWhite(true); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.addAbility(VigilanceAbility.getInstance()); + this.addAbility(HasteAbility.getInstance()); + } + + public BullCerodon (final BullCerodon card) { + super(card); + } + + @Override + public BullCerodon copy() { + return new BullCerodon(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/shardsofalara/JhessianInfiltrator.java b/Mage.Sets/src/mage/sets/shardsofalara/JhessianInfiltrator.java new file mode 100644 index 00000000000..1e88aaa616c --- /dev/null +++ b/Mage.Sets/src/mage/sets/shardsofalara/JhessianInfiltrator.java @@ -0,0 +1,67 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.sets.shardsofalara; + +import java.util.UUID; +import mage.Constants.CardType; +import mage.Constants.Duration; +import mage.Constants.Rarity; +import mage.Constants.Zone; +import mage.MageInt; +import mage.abilities.keyword.UnblockableAbility; +import mage.cards.CardImpl; + +/** + * + * @author Loki + */ +public class JhessianInfiltrator extends CardImpl { + + public JhessianInfiltrator (UUID ownerId) { + super(ownerId, 174, "Jhessian Infiltrator", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{G}{U}"); + this.expansionSetCode = "ALA"; + this.subtype.add("Human"); + this.subtype.add("Rogue"); + this.color.setGreen(true); + this.color.setBlue(true); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.addAbility(UnblockableAbility.getInstance()); + } + + public JhessianInfiltrator (final JhessianInfiltrator card) { + super(card); + } + + @Override + public JhessianInfiltrator copy() { + return new JhessianInfiltrator(this); + } + +} From f8a26d4e7e5a463f15a9307f370afd3df9bcd5c8 Mon Sep 17 00:00:00 2001 From: BetaSteward Date: Sun, 13 Feb 2011 23:48:43 -0500 Subject: [PATCH 5/8] sub-sort on name when editing decks --- .../main/java/mage/client/cards/CardGrid.java | 24 ++++++++++++++++--- .../java/mage/client/cards/CardsList.form | 2 +- .../java/mage/client/cards/CardsList.java | 24 ++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java index 7a7547e5fbd..a602188b042 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java @@ -256,7 +256,13 @@ class CardRarityComparator implements Comparator { @Override public int compare(MageCard o1, MageCard o2) { - return o1.getOriginal().getRarity().compareTo(o2.getOriginal().getRarity()); + int val = o1.getOriginal().getRarity().compareTo(o2.getOriginal().getRarity()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } + else { + return val; + } } } @@ -265,7 +271,13 @@ class CardCostComparator implements Comparator { @Override public int compare(MageCard o1, MageCard o2) { - return Integer.valueOf(o1.getOriginal().getConvertedManaCost()).compareTo(Integer.valueOf(o2.getOriginal().getConvertedManaCost())); + int val = Integer.valueOf(o1.getOriginal().getConvertedManaCost()).compareTo(Integer.valueOf(o2.getOriginal().getConvertedManaCost())); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } + else { + return val; + } } } @@ -274,7 +286,13 @@ class CardColorComparator implements Comparator { @Override public int compare(MageCard o1, MageCard o2) { - return o1.getOriginal().getColor().compareTo(o2.getOriginal().getColor()); + int val = o1.getOriginal().getColor().compareTo(o2.getOriginal().getColor()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } + else { + return val; + } } } \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/cards/CardsList.form b/Mage.Client/src/main/java/mage/client/cards/CardsList.form index 773addc57e4..38981bdff12 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardsList.form +++ b/Mage.Client/src/main/java/mage/client/cards/CardsList.form @@ -35,7 +35,7 @@ - + diff --git a/Mage.Client/src/main/java/mage/client/cards/CardsList.java b/Mage.Client/src/main/java/mage/client/cards/CardsList.java index 3b9ca08f000..60556eafef1 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardsList.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardsList.java @@ -292,7 +292,13 @@ class CardViewRarityComparator implements Comparator { @Override public int compare(CardView o1, CardView o2) { - return o1.getRarity().compareTo(o2.getRarity()); + int val = o1.getRarity().compareTo(o2.getRarity()); + if (val == 0) { + return o1.getName().compareTo(o2.getName()); + } + else { + return val; + } } } @@ -301,7 +307,13 @@ class CardViewCostComparator implements Comparator { @Override public int compare(CardView o1, CardView o2) { - return Integer.valueOf(o1.getConvertedManaCost()).compareTo(Integer.valueOf(o2.getConvertedManaCost())); + int val = Integer.valueOf(o1.getConvertedManaCost()).compareTo(Integer.valueOf(o2.getConvertedManaCost())); + if (val == 0) { + return o1.getName().compareTo(o2.getName()); + } + else { + return val; + } } } @@ -310,7 +322,13 @@ class CardViewColorComparator implements Comparator { @Override public int compare(CardView o1, CardView o2) { - return o1.getColor().compareTo(o2.getColor()); + int val = o1.getColor().compareTo(o2.getColor()); + if (val == 0) { + return o1.getName().compareTo(o2.getName()); + } + else { + return val; + } } } \ No newline at end of file From 366401b0b22ef889f4ad659557bdae18e0a20af3 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Mon, 14 Feb 2011 20:49:16 +0300 Subject: [PATCH 6/8] Updated Game interface to skip drawing. Added parsing test scenarios and updating players' zones. --- .../src/mage/player/ai/ComputerPlayer6.java | 656 ++++++++++++++++++ Mage.Tests/config/config.xml | 2 +- Mage.Tests/plugins/mage-player-ai-ma.jar | Bin 41983 -> 54938 bytes .../mage/test/serverside/PlayGameTest.java | 111 ++- .../test/serverside/base/MageTestBase.java | 3 + Mage/src/mage/game/Game.java | 14 +- Mage/src/mage/game/GameImpl.java | 115 ++- Mage/src/mage/players/Player.java | 1 + Mage/src/mage/players/PlayerImpl.java | 11 +- 9 files changed, 895 insertions(+), 18 deletions(-) create mode 100644 Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java new file mode 100644 index 00000000000..340b290b97f --- /dev/null +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -0,0 +1,656 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.player.ai; + +import mage.Constants.Outcome; +import mage.Constants.PhaseStep; +import mage.Constants.RangeOfInfluence; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.SearchEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.choices.Choice; +import mage.filter.FilterAbility; +import mage.game.Game; +import mage.game.combat.Combat; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; +import mage.game.turn.*; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetCard; +import mage.util.Logging; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author nantuko + */ +public class ComputerPlayer6 extends ComputerPlayer implements Player { + + private static final transient Logger logger = Logging.getLogger(ComputerPlayer6.class.getName()); + private static final ExecutorService pool = Executors.newFixedThreadPool(1); + + protected int maxDepth; + protected int maxNodes; + protected LinkedList actions = new LinkedList(); + protected List targets = new ArrayList(); + protected List choices = new ArrayList(); + protected Combat combat; + protected int currentScore; + protected SimulationNode2 root; + + public ComputerPlayer6(String name, RangeOfInfluence range) { + super(name, range); + maxDepth = Config2.maxDepth; + maxNodes = Config2.maxNodes; + } + + public ComputerPlayer6(final ComputerPlayer6 player) { + super(player); + this.maxDepth = player.maxDepth; + this.currentScore = player.currentScore; + if (player.combat != null) + this.combat = player.combat.copy(); + for (Ability ability: player.actions) { + actions.add(ability); + } + for (UUID targetId: player.targets) { + targets.add(targetId); + } + for (String choice: player.choices) { + choices.add(choice); + } + } + + @Override + public ComputerPlayer6 copy() { + return new ComputerPlayer6(this); + } + + @Override + public void priority(Game game) { + logState(game); + game.firePriorityEvent(playerId); + switch (game.getTurn().getStepType()) { + case UPKEEP: + case DRAW: + pass(); + break; + case PRECOMBAT_MAIN: + case POSTCOMBAT_MAIN: + if (game.getActivePlayerId().equals(playerId)) { + Player player = game.getPlayer(playerId); + System.out.println("Turn::"+game.getTurnNum()); + System.out.println("[" + game.getPlayer(playerId).getName() + "] " + game.getTurn().getStepType().name() +", life=" + player.getLife()); + String s = "["; + for (Card card : player.getHand().getCards(game)) { + s += card.getName() + ";"; + } + s += "]"; + System.out.println("Hand: " + s); + s = "["; + for (Permanent permanent : game.getBattlefield().getAllPermanents()) { + if (permanent.getOwnerId().equals(player.getId())) { + s += permanent.getName() + ";"; + } + } + s += "]"; + System.out.println("Permanents: " + s); + } + if (actions.size() == 0) { + calculateActions(game); + } + act(game); + break; + case BEGIN_COMBAT: + case DECLARE_ATTACKERS: + case DECLARE_BLOCKERS: + case COMBAT_DAMAGE: + case END_COMBAT: + case END_TURN: + pass(); + break; + case CLEANUP: + pass(); + break; + } + } + + protected void act(Game game) { + if (actions == null || actions.size() == 0) + pass(); + else { + boolean usedStack = false; + while (actions.peek() != null) { + Ability ability = actions.poll(); + System.out.println("[" + game.getPlayer(playerId).getName() + "] Action: " + ability.toString()); + this.activateAbility((ActivatedAbility) ability, game); + if (ability.isUsesStack()) + usedStack = true; + } + if (usedStack) + pass(); + } + } + + protected void calculateActions(Game game) { + currentScore = GameStateEvaluator2.evaluate(playerId, game); + if (!getNextAction(game)) { + Game sim = createSimulation(game); + SimulationNode2.resetCount(); + root = new SimulationNode2(sim, maxDepth, playerId); + logger.info("simulating actions"); + addActionsTimed(new FilterAbility()); + if (root.children.size() > 0) { + root = root.children.get(0); + int bestScore = GameStateEvaluator2.evaluate(playerId, root.getGame()); + if (bestScore > currentScore) { + actions = new LinkedList(root.abilities); + combat = root.combat; + } + } + } + } + + protected boolean getNextAction(Game game) { + if (root != null && root.children.size() > 0) { + SimulationNode2 test = root; + root = root.children.get(0); + while (root.children.size() > 0 && !root.playerId.equals(playerId)) { + test = root; + root = root.children.get(0); + } + logger.info("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); + if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue() == test.gameValue) { + logger.info("simulating -- continuing previous action chain"); + actions = new LinkedList(root.abilities); + combat = root.combat; + return true; + } + else { + return false; + } + } + return false; + } + + protected int minimaxAB(SimulationNode2 node, FilterAbility filter, int depth, int alpha, int beta) { + UUID currentPlayerId = node.getGame().getPlayerList().get(); + SimulationNode2 bestChild = null; + for (SimulationNode2 child: node.getChildren()) { + if (alpha >= beta) { + logger.info("alpha beta pruning"); + break; + } + if (SimulationNode2.nodeCount > maxNodes) { + logger.info("simulating -- reached end-state, count=" + SimulationNode2.nodeCount); + break; + } + int val = addActions(child, filter, depth-1, alpha, beta); + if (!currentPlayerId.equals(playerId)) { + if (val < beta) { + beta = val; + bestChild = child; + if (node.getCombat() == null) + node.setCombat(child.getCombat()); + } + } + else { + if (val > alpha) { + alpha = val; + bestChild = child; + if (node.getCombat() == null) + node.setCombat(child.getCombat()); + } + } + } + node.children.clear(); + if (bestChild != null) + node.children.add(bestChild); + if (!currentPlayerId.equals(playerId)) { + //logger.info("returning minimax beta: " + beta); + return beta; + } + else { + //logger.info("returning minimax alpha: " + alpha); + return alpha; + } + } + + protected SearchEffect getSearchEffect(StackAbility ability) { + for (Effect effect: ability.getEffects()) { + if (effect instanceof SearchEffect) { + return (SearchEffect) effect; + } + } + return null; + } + + protected void resolve(SimulationNode2 node, int depth, Game game) { + StackObject ability = game.getStack().pop(); + if (ability instanceof StackAbility) { + SearchEffect effect = getSearchEffect((StackAbility) ability); + if (effect != null && ability.getControllerId().equals(playerId)) { + Target target = effect.getTarget(); + if (!target.doneChosing()) { + for (UUID targetId: target.possibleTargets(ability.getSourceId(), ability.getControllerId(), game)) { + Game sim = game.copy(); + StackAbility newAbility = (StackAbility) ability.copy(); + SearchEffect newEffect = getSearchEffect((StackAbility) newAbility); + newEffect.getTarget().addTarget(targetId, newAbility, sim); + sim.getStack().push(newAbility); + SimulationNode2 newNode = new SimulationNode2(sim, depth, ability.getControllerId()); + node.children.add(newNode); + newNode.getTargets().add(targetId); + logger.fine("simulating search -- node#: " + SimulationNode2.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); + } + return; + } + } + } + //logger.info("simulating resolve "); + ability.resolve(game); + game.applyEffects(); + game.getPlayers().resetPassed(); + game.getPlayerList().setCurrent(game.getActivePlayerId()); + } + + protected void addActionsTimed(final FilterAbility filter) { + FutureTask task = new FutureTask(new Callable() { + public Integer call() throws Exception + { + return addActions(root, filter, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + }); + pool.execute(task); + try { + task.get(Config2.maxThinkSeconds, TimeUnit.MINUTES); + } catch (TimeoutException e) { + logger.info("simulating - timed out"); + task.cancel(true); + } catch (ExecutionException e) { + e.printStackTrace(); + task.cancel(true); + } catch (InterruptedException e) { + e.printStackTrace(); + task.cancel(true); + } + } + + protected int addActions(SimulationNode2 node, FilterAbility filter, int depth, int alpha, int beta) { + logger.fine("addActions: " + depth + ", alpha=" + alpha + ", beta=" + beta); + Game game = node.getGame(); + int val; + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + logger.info("interrupted"); + val = GameStateEvaluator2.evaluate(playerId, game); + return val; + } + if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.isGameOver()) { + logger.fine("simulating -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth); + val = GameStateEvaluator2.evaluate(playerId, game); + return val; + } + else if (node.getChildren().size() > 0) { + logger.fine("simulating -- somthing added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, depth-1, alpha, beta); + return val; + } + else { + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId) ? "yes" : "no")); + if (allPassed(game)) { + if (!game.getStack().isEmpty()) { + resolve(node, depth, game); + } + else { + game.getPlayers().resetPassed(); + playNext(game, game.getActivePlayerId(), node); + } + } + + if (game.isGameOver()) { + val = GameStateEvaluator2.evaluate(playerId, game); + } + else if (node.getChildren().size() > 0) { + //declared attackers or blockers or triggered abilities + logger.fine("simulating -- attack/block/trigger added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, depth-1, alpha, beta); + } + else { + val = simulatePriority(node, game, filter, depth, alpha, beta); + } + } + + if (logger.isLoggable(Level.FINE)) + logger.fine("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + return val; + + } + + protected int simulatePriority(SimulationNode2 node, Game game, FilterAbility filter, int depth, int alpha, int beta) { + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + logger.info("interrupted"); + return GameStateEvaluator2.evaluate(playerId, game); + } + node.setGameValue(game.getState().getValue()); + SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get()); + //logger.info("simulating -- player " + currentPlayer.getName()); + SimulationNode2 bestNode = null; + List allActions = currentPlayer.simulatePriority(game, filter); + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- adding " + allActions.size() + " children:" + allActions); + for (Ability action: allActions) { + Game sim = game.copy(); + if (sim.getPlayer(currentPlayer.getId()).activateAbility((ActivatedAbility) action.copy(), sim)) { + sim.applyEffects(); + if (!sim.isGameOver() && action.isUsesStack()) { + // only pass if the last action uses the stack + sim.getPlayer(currentPlayer.getId()).pass(); + sim.getPlayerList().getNext(); + } + SimulationNode2 newNode = new SimulationNode2(sim, action, depth, currentPlayer.getId()); + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- node #:" + SimulationNode2.getCount() + " actions:" + action); + sim.checkStateAndTriggered(); + int val = addActions(newNode, filter, depth-1, alpha, beta); + if (!currentPlayer.getId().equals(playerId)) { + if (val < beta) { + beta = val; + bestNode = newNode; + node.setCombat(newNode.getCombat()); + } + } + else { + if (val > alpha) { + alpha = val; + bestNode = newNode; + node.setCombat(newNode.getCombat()); + if (node.getTargets().size() > 0) + targets = node.getTargets(); + if (node.getChoices().size() > 0) + choices = node.getChoices(); + } + } + if (alpha >= beta) { + //logger.info("simulating -- pruning"); + break; + } + if (SimulationNode2.nodeCount > maxNodes) { + logger.fine("simulating -- reached end-state"); + break; + } + } + } + if (bestNode != null) { + node.children.clear(); + node.children.add(bestNode); + } + if (!currentPlayer.getId().equals(playerId)) { + //logger.info("returning priority beta: " + beta); + return beta; + } + else { + //logger.info("returning priority alpha: " + alpha); + return alpha; + } + } + + protected boolean allPassed(Game game) { + for (Player player: game.getPlayers().values()) { + if (!player.isPassed() && !player.hasLost() && !player.hasLeft()) + return false; + } + return true; + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + if (choices.size() == 0) + return super.choose(outcome, choice, game); + if (!choice.isChosen()) { + for (String achoice: choices) { + choice.setChoice(achoice); + if (choice.isChosen()) { + choices.clear(); + return true; + } + } + return false; + } + return true; + } + + @Override + public boolean chooseTarget(Cards cards, TargetCard target, Ability source, Game game) { + if (targets.size() == 0) + return super.chooseTarget(cards, target, source, game); + if (!target.doneChosing()) { + for (UUID targetId: targets) { + target.addTarget(targetId, source, game); + if (target.doneChosing()) { + targets.clear(); + return true; + } + } + return false; + } + return true; + } + + @Override + public boolean choose(Cards cards, TargetCard target, Game game) { + if (targets.size() == 0) + return super.choose(cards, target, game); + if (!target.doneChosing()) { + for (UUID targetId: targets) { + target.add(targetId, game); + if (target.doneChosing()) { + targets.clear(); + return true; + } + } + return false; + } + return true; + } + + public void playNext(Game game, UUID activePlayerId, SimulationNode2 node) { + boolean skip = false; + while (true) { + Phase currentPhase = game.getPhase(); + if (!skip) + currentPhase.getStep().endStep(game, activePlayerId); + game.applyEffects(); + switch (currentPhase.getStep().getType()) { + case UNTAP: + game.getPhase().setStep(new UpkeepStep()); + break; + case UPKEEP: + game.getPhase().setStep(new DrawStep()); + break; + case DRAW: + game.getTurn().setPhase(new PreCombatMainPhase()); + game.getPhase().setStep(new PreCombatMainStep()); + break; + case PRECOMBAT_MAIN: + game.getTurn().setPhase(new CombatPhase()); + game.getPhase().setStep(new BeginCombatStep()); + break; + case BEGIN_COMBAT: + game.getPhase().setStep(new DeclareAttackersStep()); + break; + case DECLARE_ATTACKERS: + game.getPhase().setStep(new DeclareBlockersStep()); + break; + case DECLARE_BLOCKERS: + game.getPhase().setStep(new CombatDamageStep(true)); + break; + case COMBAT_DAMAGE: + if (((CombatDamageStep)currentPhase.getStep()).getFirst()) + game.getPhase().setStep(new CombatDamageStep(false)); + else + game.getPhase().setStep(new EndOfCombatStep()); + break; + case END_COMBAT: + game.getTurn().setPhase(new PostCombatMainPhase()); + game.getPhase().setStep(new PostCombatMainStep()); + break; + case POSTCOMBAT_MAIN: + game.getTurn().setPhase(new EndPhase()); + game.getPhase().setStep(new EndStep()); + break; + case END_TURN: + game.getPhase().setStep(new CleanupStep()); + break; + case CLEANUP: + game.getPhase().getStep().beginStep(game, activePlayerId); + if (!game.checkStateAndTriggered() && !game.isGameOver()) { + game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); + game.getTurn().setPhase(new BeginningPhase()); + game.getPhase().setStep(new UntapStep()); + } + } + if (!game.getStep().skipStep(game, game.getActivePlayerId())) { + if (game.getTurn().getStepType() == PhaseStep.DECLARE_ATTACKERS) { + game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, activePlayerId)); + if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) { + for (Combat engagement: ((SimulatedPlayer2)game.getPlayer(activePlayerId)).addAttackers(game)) { + Game sim = game.copy(); + UUID defenderId = game.getOpponents(playerId).iterator().next(); + for (CombatGroup group: engagement.getGroups()) { + for (UUID attackerId: group.getAttackers()) { + sim.getPlayer(activePlayerId).declareAttacker(attackerId, defenderId, sim); + } + } + sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, playerId, playerId)); + SimulationNode2 newNode = new SimulationNode2(sim, node.getDepth()-1, activePlayerId); + logger.info("simulating -- node #:" + SimulationNode2.getCount() + " declare attakers"); + newNode.setCombat(sim.getCombat()); + node.children.add(newNode); + } + } + } + else if (game.getTurn().getStepType() == PhaseStep.DECLARE_BLOCKERS) { + game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_PRE, null, null, activePlayerId)); + if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, activePlayerId, activePlayerId))) { + for (UUID defenderId: game.getCombat().getDefenders()) { + //check if defender is being attacked + if (game.getCombat().isAttacked(defenderId, game)) { + for (Combat engagement: ((SimulatedPlayer2)game.getPlayer(defenderId)).addBlockers(game)) { + Game sim = game.copy(); + for (CombatGroup group: engagement.getGroups()) { + for (UUID blockerId: group.getBlockers()) { + group.addBlocker(blockerId, defenderId, sim); + } + } + sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); + SimulationNode2 newNode = new SimulationNode2(sim, node.getDepth()-1, defenderId); + logger.info("simulating -- node #:" + SimulationNode2.getCount() + " declare blockers"); + newNode.setCombat(sim.getCombat()); + node.children.add(newNode); + } + } + } + } + } + else { + game.getStep().beginStep(game, activePlayerId); + } + if (game.getStep().getHasPriority()) + break; + } + else { + skip = true; + } + } + game.checkStateAndTriggered(); + } + + @Override + public void selectAttackers(Game game) { + logger.info("selectAttackers"); + if (combat != null) { + UUID opponentId = game.getCombat().getDefenders().iterator().next(); + for (UUID attackerId: combat.getAttackers()) { + logger.info("declare attacker: " + game.getCard(attackerId).getName()); + this.declareAttacker(attackerId, opponentId, game); + } + } + } + + @Override + public void selectBlockers(Game game) { + logger.info("selectBlockers"); + if (combat != null && combat.getGroups().size() > 0) { + List groups = game.getCombat().getGroups(); + for (int i = 0; i < groups.size(); i++) { + if (i < combat.getGroups().size()) { + for (UUID blockerId: combat.getGroups().get(i).getBlockers()) { + this.declareBlocker(blockerId, groups.get(i).getAttackers().get(0), game); + } + } + } + } + } + + /** + * Copies game and replaces all players in copy with simulated players + * + * @param game + * @return a new game object with simulated players + */ + protected Game createSimulation(Game game) { + Game sim = game.copy(); + + for (Player copyPlayer: sim.getState().getPlayers().values()) { + Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()); + SimulatedPlayer2 newPlayer = new SimulatedPlayer2(copyPlayer.getId(), copyPlayer.getId().equals(playerId)); + newPlayer.restore(origPlayer); + sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); + } + return sim; + } + +} diff --git a/Mage.Tests/config/config.xml b/Mage.Tests/config/config.xml index 763e09187a1..7af45388f85 100644 --- a/Mage.Tests/config/config.xml +++ b/Mage.Tests/config/config.xml @@ -4,7 +4,7 @@ - + diff --git a/Mage.Tests/plugins/mage-player-ai-ma.jar b/Mage.Tests/plugins/mage-player-ai-ma.jar index 8792f2746e5327201c3dd440fa01359f1760c57b..6e895cae6c74e391b082288ff67dcbecb7e67917 100644 GIT binary patch delta 14107 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
Q6 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 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 e8fed69414d..157e8001ca9 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 @@ -1,6 +1,8 @@ package org.mage.test.serverside; import mage.Constants; +import mage.cards.Card; +import mage.cards.ExpansionSet; import mage.cards.decks.Deck; import mage.game.Game; import mage.game.GameException; @@ -11,42 +13,137 @@ import mage.sets.Sets; import org.junit.Test; import org.mage.test.serverside.base.MageTestBase; +import java.io.File; import java.io.FileNotFoundException; +import java.util.*; +import java.util.regex.Matcher; /** * @author ayratn */ 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); - Player player = createPlayer("computer1", "Computer - mad"); + Player computerA = createPlayer("ComputerA", "Computer - mad"); Deck deck = Deck.load(Sets.loadDeck("RB Aggro.dck")); if (deck.getCards().size() < 40) { throw new IllegalArgumentException("Couldn't load deck, deck side=" + deck.getCards().size()); } - game.addPlayer(player, deck); - game.loadCards(deck.getCards(), player.getId()); + game.addPlayer(computerA, deck); + game.loadCards(deck.getCards(), computerA.getId()); - Player player2 = createPlayer("computer2", "Computer - mad"); + Player computerB = createPlayer("ComputerB", "Computer - mad"); Deck deck2 = Deck.load(Sets.loadDeck("RB Aggro.dck")); if (deck2.getCards().size() < 40) { throw new IllegalArgumentException("Couldn't load deck, deck side=" + deck2.getCards().size()); } - game.addPlayer(player2, deck2); - game.loadCards(deck2.getCards(), player2.getId()); + game.addPlayer(computerB, deck2); + game.loadCards(deck2.getCards(), computerB.getId()); + + parseScenario("scenario1.txt"); + game.cheat(computerA.getId(), commandsA); + game.cheat(computerA.getId(), libraryCardsA, handCardsA, battlefieldCardsA, graveyardCardsA); + game.cheat(computerB.getId(), commandsB); + game.cheat(computerB.getId(), libraryCardsB, handCardsB, battlefieldCardsB, graveyardCardsB); long t1 = System.nanoTime(); - game.start(player.getId()); + game.start(computerA.getId(), true); long t2 = System.nanoTime(); logger.info("Winner: " + game.getWinner()); logger.info("Time: " + (t2 - t1) / 1000000 + " ms"); } + 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.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; + 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; + cards = 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 { + continue; // go parse next line + } + + String cardName = m.group(3); + Integer amount = Integer.parseInt(m.group(4)); + + 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) { + cards.add(card); + } else { + logger.severe("Couldn't find a card: " + cardName); + logger.severe("line: " + line); + } + } + } + } else { + logger.warning("Unknown player: " + nickname); + } + } else { + logger.warning("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/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index 56e319cb6c3..c9a0700086e 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 @@ -18,6 +18,7 @@ import java.io.File; import java.io.FilenameFilter; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; /** * @author ayratn @@ -29,6 +30,8 @@ public class MageTestBase { private final static String pluginFolder = "plugins"; + protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)"); + @BeforeClass public static void init() { logger.info("Starting MAGE tests"); diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 50550e934df..b0b18e30d33 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -28,15 +28,14 @@ package mage.game; +import mage.Constants; import mage.game.match.MatchType; import mage.cards.Card; import mage.game.stack.SpellStack; import mage.MageObject; import java.io.Serializable; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; + import mage.Constants.MultiplayerAttackOption; import mage.Constants.RangeOfInfluence; import mage.Constants.Zone; @@ -61,6 +60,7 @@ import mage.game.permanent.Permanent; import mage.game.turn.Phase; import mage.game.turn.Step; import mage.game.turn.Turn; +import mage.players.Library; import mage.players.Player; import mage.players.PlayerList; import mage.players.Players; @@ -136,8 +136,9 @@ public interface Game extends MageItem, Serializable { public boolean replaceEvent(GameEvent event); //game play methods -// public void init(UUID choosingPlayerId); + //public void init(UUID choosingPlayerId); public void start(UUID choosingPlayerId); + public void start(UUID choosingPlayerId, boolean testMode); public void end(); public void mulligan(UUID playerId); public void quit(UUID playerId); @@ -156,4 +157,7 @@ public interface Game extends MageItem, Serializable { public void restoreState(); public void removeLastBookmark(); + // game cheats (for tests only) + public void cheat(UUID ownerId, Map commands); + public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard); } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index c841c089cee..09a801fe5ce 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -47,11 +47,13 @@ import mage.game.events.*; import mage.game.events.TableEvent.EventType; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; import mage.game.stack.SpellStack; import mage.game.stack.StackObject; import mage.game.turn.Phase; import mage.game.turn.Step; import mage.game.turn.Turn; +import mage.players.Library; import mage.players.Player; import mage.players.PlayerList; import mage.players.Players; @@ -289,7 +291,12 @@ public abstract class GameImpl> implements Game, Serializa @Override public void start(UUID choosingPlayerId) { - init(choosingPlayerId); + start(choosingPlayerId, false); + } + + @Override + public void start(UUID choosingPlayerId, boolean testMode) { + init(choosingPlayerId, testMode); PlayerList players = state.getPlayerList(startingPlayerId); Player player = getPlayer(players.get()); while (!isGameOver()) { @@ -311,8 +318,12 @@ public abstract class GameImpl> implements Game, Serializa } protected void init(UUID choosingPlayerId) { + init(choosingPlayerId, false); + } + + protected void init(UUID choosingPlayerId, boolean testMode) { for (Player player: state.getPlayers().values()) { - player.init(this); + player.init(this, testMode); } fireInformEvent("game has started"); saveState(); @@ -347,7 +358,9 @@ public abstract class GameImpl> implements Game, Serializa for (UUID playerId: state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); player.setLife(this.getLife(), this); - player.drawCards(7, this); + if (!testMode) { + player.drawCards(7, this); + } } //20091005 - 103.4 @@ -918,4 +931,100 @@ public abstract class GameImpl> implements Game, Serializa public void resetLKI() { lki.clear(); } + + public void cheat(UUID ownerId, Map commands) { + if (commands != null) { + Player player = getPlayer(ownerId); + if (player != null) { + for (Map.Entry command : commands.entrySet()) { + switch (command.getKey()) { + case HAND: + if (command.getValue().equals("clear")) { + removeCards(player.getHand()); + } + break; + case LIBRARY: + if (command.getValue().equals("clear")) { + for (UUID card : player.getLibrary().getCardList()) { + gameCards.remove(card); + } + player.getLibrary().clear(); + } + break; + } + } + } + } + } + + private void removeCards(Cards cards) { + for (UUID card : cards) { + gameCards.remove(card); + } + cards.clear(); + } + + public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard) { + Player player = getPlayer(ownerId); + if (player != null) { + loadCards(ownerId, library); + loadCards(ownerId, hand); + loadCards(ownerId, battlefield); + loadCards(ownerId, graveyard); + + for (Card card : library) { + setZone(card.getId(), Zone.LIBRARY); + player.getLibrary().putOnTop(card, this); + } + for (Card card : hand) { + setZone(card.getId(), Zone.HAND); + player.getHand().add(card); + } + for (Card card : graveyard) { + setZone(card.getId(), Zone.GRAVEYARD); + player.getGraveyard().add(card); + } + List permanents = new ArrayList(); + for (Card card : battlefield) { + card.setOwnerId(ownerId); + PermanentCard permanent = new PermanentCard(card, ownerId); + getBattlefield().addPermanent(permanent); + } + applyEffects(); + } + } + + private void loadCards(UUID ownerId, List cards) { + if (cards == null) { + return; + } + Set set = new HashSet(); + for (Card card : cards) { + set.add(card); + } + loadCards(set, ownerId); + } + + public void replaceLibrary(List cardsDownToTop, UUID ownerId) { + Player player = getPlayer(ownerId); + if (player != null) { + for (UUID card : player.getLibrary().getCardList()) { + gameCards.remove(card); + } + player.getLibrary().clear(); + Set cards = new HashSet(); + for (Card card : cardsDownToTop) { + cards.add(card); + } + loadCards(cards, ownerId); + + for (Card card : cards) { + player.getLibrary().putOnTop(card, this); + } + } + } + + public void clearGraveyard(UUID playerId) { + + } } diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 9749716a47c..a3fe979ca7c 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -97,6 +97,7 @@ public interface Player extends MageItem, Copyable { public Set getInRange(); public void init(Game game); + public void init(Game game, boolean testMode); public void useDeck(Deck deck, Game game); public void reset(); public void shuffleLibrary(Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 48d1152861b..f1b12a0adb3 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -152,9 +152,16 @@ public abstract class PlayerImpl> implements Player, Ser @Override public void init(Game game) { + init(game, false); + } + + @Override + public void init(Game game, boolean testMode) { this.abort = false; - this.hand.clear(); - this.graveyard.clear(); + if (!testMode) { + this.hand.clear(); + this.graveyard.clear(); + } this.abilities.clear(); this.wins = false; this.loses = false; From c8fcc25ba273ee0cbd28e4249a62c64e97d1dcbf Mon Sep 17 00:00:00 2001 From: magenoxx Date: Mon, 14 Feb 2011 20:49:52 +0300 Subject: [PATCH 7/8] First test scenario. --- Mage.Tests/scenario1.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Mage.Tests/scenario1.txt diff --git a/Mage.Tests/scenario1.txt b/Mage.Tests/scenario1.txt new file mode 100644 index 00000000000..cf7e85f73bf --- /dev/null +++ b/Mage.Tests/scenario1.txt @@ -0,0 +1,21 @@ +### ComputerA ### +# Battlefield +battlefield:ComputerA:Island:1 +# Hand +hand:ComputerA:Mountain:2 +hand:ComputerA:Lightning Bolt:5 +# Library +# from down to top +library:ComputerA:clear:0 +library:ComputerA:Lightning Bolt:10 + +### ComputerB ### +# Battlefield +battlefield:ComputerB:Plains:1 +# Hand +hand:ComputerB:Plains:2 +# Library +# from down to top +library:ComputerB:clear:0 +library:ComputerB:Plains:10 + From 0c5261ce8810c388b23533cfcd9401f3be12df03 Mon Sep 17 00:00:00 2001 From: BetaSteward Date: Mon, 14 Feb 2011 14:41:29 -0500 Subject: [PATCH 8/8] fixed canTarget error and started to convert logging to log4j --- .../java/mage/player/ai/ComputerPlayer.java | 2 +- .../Mage.Player.AIMinimax/pom.xml | 6 ++ .../src/mage/player/ai/ComputerPlayer2.java | 76 ++++++++-------- .../src/mage/player/ai/ComputerPlayer3.java | 91 +++++++++---------- .../src/mage/player/ai/Config.java | 10 +- .../src/mage/player/ai/SimulatedPlayer.java | 32 +++---- .../src/main/java/mage/server/Main.java | 22 ++--- .../mage/test/serverside/PlayGameTest.java | 8 +- .../test/serverside/base/MageTestBase.java | 22 ++--- .../target/common/TargetCreatureOrPlayer.java | 9 +- 10 files changed, 137 insertions(+), 141 deletions(-) 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 e0eb3d3540f..399f7348d5e 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 @@ -280,7 +280,7 @@ public class ComputerPlayer> extends PlayerImpl i targets = threats(opponentId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game); } for (Permanent permanent: targets) { - if (((TargetPermanent)target).canTarget(playerId, permanent.getId(), source, game)) { + if (t.canTarget(playerId, permanent.getId(), source, game)) { target.addTarget(permanent.getId(), source, game); return true; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml index 321eec47b4e..8e98cf01ebe 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml @@ -15,6 +15,12 @@ Mage Player AI Minimax + + log4j + log4j + 1.2.14 + jar + ${project.groupId} Mage diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java index 5a30fd91840..ab15d6f8daa 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java @@ -39,8 +39,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.logging.Level; -import java.util.logging.Logger; import mage.Constants.Outcome; import mage.Constants.PhaseStep; import mage.Constants.RangeOfInfluence; @@ -79,7 +77,7 @@ import mage.game.turn.UpkeepStep; import mage.players.Player; import mage.target.Target; import mage.target.TargetCard; -import mage.util.Logging; +import org.apache.log4j.Logger; /** * @@ -87,7 +85,7 @@ import mage.util.Logging; */ public class ComputerPlayer2 extends ComputerPlayer implements Player { - private static final transient Logger logger = Logging.getLogger(ComputerPlayer2.class.getName()); + private static final transient Logger logger = Logger.getLogger(ComputerPlayer2.class); private static final ExecutorService pool = Executors.newFixedThreadPool(1); protected int maxDepth; @@ -177,7 +175,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Game sim = createSimulation(game); SimulationNode.resetCount(); root = new SimulationNode(sim, maxDepth, playerId); - logger.fine("simulating actions"); + logger.debug("simulating actions"); addActionsTimed(new FilterAbility()); if (root.children.size() > 0) { root = root.children.get(0); @@ -195,9 +193,9 @@ public class ComputerPlayer2 extends ComputerPlayer implements test = root; root = root.children.get(0); } - logger.fine("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); + logger.debug("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue() == test.gameValue) { - logger.fine("simulating -- continuing previous action chain"); + logger.debug("simulating -- continuing previous action chain"); actions = new LinkedList(root.abilities); combat = root.combat; return true; @@ -214,11 +212,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements SimulationNode bestChild = null; for (SimulationNode child: node.getChildren()) { if (alpha >= beta) { - logger.fine("alpha beta pruning"); + logger.debug("alpha beta pruning"); break; } if (SimulationNode.nodeCount > maxNodes) { - logger.fine("simulating -- reached end-state"); + logger.debug("simulating -- reached end-state"); break; } int val = addActions(child, filter, depth-1, alpha, beta); @@ -243,11 +241,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements if (bestChild != null) node.children.add(bestChild); if (!currentPlayerId.equals(playerId)) { - logger.fine("returning minimax beta: " + beta); + logger.debug("returning minimax beta: " + beta); return beta; } else { - logger.fine("returning minimax alpha: " + alpha); + logger.debug("returning minimax alpha: " + alpha); return alpha; } } @@ -277,13 +275,13 @@ public class ComputerPlayer2 extends ComputerPlayer implements SimulationNode newNode = new SimulationNode(sim, depth, ability.getControllerId()); node.children.add(newNode); newNode.getTargets().add(targetId); - logger.fine("simulating search -- node#: " + SimulationNode.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); + logger.debug("simulating search -- node#: " + SimulationNode.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); } return; } } } - logger.fine("simulating resolve "); + logger.debug("simulating resolve "); ability.resolve(game); game.applyEffects(); game.getPlayers().resetPassed(); @@ -301,13 +299,13 @@ public class ComputerPlayer2 extends ComputerPlayer implements try { task.get(Config.maxThinkSeconds, TimeUnit.SECONDS); } catch (TimeoutException e) { - logger.fine("simulating - timed out"); + logger.debug("simulating - timed out"); task.cancel(true); } catch (ExecutionException e) { - logger.log(Level.SEVERE, "Simulation error", e); + logger.fatal("Simulation error", e); task.cancel(true); } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Simulation interrupted", e); + logger.fatal("Simulation interrupted", e); task.cancel(true); } } @@ -317,20 +315,20 @@ public class ComputerPlayer2 extends ComputerPlayer implements int val; if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } if (depth <= 0 || SimulationNode.nodeCount > maxNodes || game.isGameOver()) { - logger.fine("simulating -- reached end state"); + logger.debug("simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } else if (node.getChildren().size() > 0) { - logger.fine("simulating -- somthing added children:" + node.getChildren().size()); + logger.debug("simulating -- somthing added children:" + node.getChildren().size()); val = minimaxAB(node, filter, depth-1, alpha, beta); } else { - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId)?"yes":"no")); + if (logger.isDebugEnabled()) + logger.debug("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId)?"yes":"no")); if (allPassed(game)) { if (!game.getStack().isEmpty()) { resolve(node, depth, game); @@ -339,7 +337,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements // int testScore = GameStateEvaluator.evaluate(playerId, game); // if (testScore < currentScore) { // // if score at end of step is worse than original score don't check any further -// logger.fine("simulating -- abandoning current check, no immediate benefit"); +// logger.debug("simulating -- abandoning current check, no immediate benefit"); // return testScore; // } game.getPlayers().resetPassed(); @@ -352,7 +350,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements } else if (node.getChildren().size() > 0) { //declared attackers or blockers or triggered abilities - logger.fine("simulating -- attack/block/trigger added children:" + node.getChildren().size()); + logger.debug("simulating -- attack/block/trigger added children:" + node.getChildren().size()); val = minimaxAB(node, filter, depth-1, alpha, beta); } else { @@ -360,8 +358,8 @@ public class ComputerPlayer2 extends ComputerPlayer implements } } - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } @@ -369,16 +367,16 @@ public class ComputerPlayer2 extends ComputerPlayer implements protected int simulatePriority(SimulationNode node, Game game, FilterAbility filter, int depth, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } node.setGameValue(game.getState().getValue()); SimulatedPlayer currentPlayer = (SimulatedPlayer) game.getPlayer(game.getPlayerList().get()); - logger.fine("simulating -- player " + currentPlayer.getName()); + logger.debug("simulating -- player " + currentPlayer.getName()); SimulationNode bestNode = null; List allActions = currentPlayer.simulatePriority(game, filter); - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- adding " + allActions.size() + " children:" + allActions); + if (logger.isDebugEnabled()) + logger.debug("simulating -- adding " + allActions.size() + " children:" + allActions); for (Ability action: allActions) { Game sim = game.copy(); if (sim.getPlayer(currentPlayer.getId()).activateAbility((ActivatedAbility) action.copy(), sim)) { @@ -389,8 +387,8 @@ public class ComputerPlayer2 extends ComputerPlayer implements sim.getPlayerList().getNext(); } SimulationNode newNode = new SimulationNode(sim, action, depth, currentPlayer.getId()); - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- node #:" + SimulationNode.getCount() + " actions:" + action); + if (logger.isDebugEnabled()) + logger.debug("simulating -- node #:" + SimulationNode.getCount() + " actions:" + action); sim.checkStateAndTriggered(); int val = addActions(newNode, filter, depth-1, alpha, beta); if (!currentPlayer.getId().equals(playerId)) { @@ -412,11 +410,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements } } if (alpha >= beta) { - logger.fine("simulating -- pruning"); + logger.debug("simulating -- pruning"); break; } if (SimulationNode.nodeCount > maxNodes) { - logger.fine("simulating -- reached end-state"); + logger.debug("simulating -- reached end-state"); break; } } @@ -426,11 +424,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements node.children.add(bestNode); } if (!currentPlayer.getId().equals(playerId)) { - logger.fine("returning priority beta: " + beta); + logger.debug("returning priority beta: " + beta); return beta; } else { - logger.fine("returning priority alpha: " + alpha); + logger.debug("returning priority alpha: " + alpha); return alpha; } } @@ -564,7 +562,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, playerId, playerId)); SimulationNode newNode = new SimulationNode(sim, node.getDepth()-1, activePlayerId); - logger.fine("simulating -- node #:" + SimulationNode.getCount() + " declare attakers"); + logger.debug("simulating -- node #:" + SimulationNode.getCount() + " declare attakers"); newNode.setCombat(sim.getCombat()); node.children.add(newNode); } @@ -585,7 +583,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); SimulationNode newNode = new SimulationNode(sim, node.getDepth()-1, defenderId); - logger.fine("simulating -- node #:" + SimulationNode.getCount() + " declare blockers"); + logger.debug("simulating -- node #:" + SimulationNode.getCount() + " declare blockers"); newNode.setCombat(sim.getCombat()); node.children.add(newNode); } @@ -608,7 +606,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements @Override public void selectAttackers(Game game) { - logger.fine("selectAttackers"); + logger.debug("selectAttackers"); if (combat != null) { UUID opponentId = game.getCombat().getDefenders().iterator().next(); for (UUID attackerId: combat.getAttackers()) { @@ -619,7 +617,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements @Override public void selectBlockers(Game game) { - logger.fine("selectBlockers"); + logger.debug("selectBlockers"); if (combat != null && combat.getGroups().size() > 0) { List groups = game.getCombat().getGroups(); for (int i = 0; i < groups.size(); i++) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java index b3f41345278..2e8a2b2dad2 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java @@ -30,14 +30,11 @@ package mage.player.ai; import java.util.LinkedList; import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; import mage.Constants.AbilityType; import mage.Constants.PhaseStep; import mage.Constants.RangeOfInfluence; import mage.Constants.Zone; import mage.abilities.Ability; -import mage.cards.decks.Deck; import mage.filter.FilterAbility; import mage.game.Game; import mage.game.combat.Combat; @@ -60,7 +57,7 @@ import mage.game.turn.Step; import mage.game.turn.UntapStep; import mage.game.turn.UpkeepStep; import mage.players.Player; -import mage.util.Logging; +import org.apache.log4j.Logger; /** * @@ -68,7 +65,7 @@ import mage.util.Logging; */ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { - private static final transient Logger logger = Logging.getLogger(ComputerPlayer3.class.getName()); + private static final transient Logger logger = Logger.getLogger(ComputerPlayer3.class); private static FilterAbility filterLand = new FilterAbility(); private static FilterAbility filterNotLand = new FilterAbility(); @@ -158,7 +155,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { Game sim = createSimulation(game); SimulationNode.resetCount(); root = new SimulationNode(sim, maxDepth, playerId); - logger.fine("simulating pre combat actions -----------------------------------------------------------------------------------------"); + logger.debug("simulating pre combat actions -----------------------------------------------------------------------------------------"); addActionsTimed(new FilterAbility()); if (root.children.size() > 0) { @@ -175,7 +172,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { Game sim = createSimulation(game); SimulationNode.resetCount(); root = new SimulationNode(sim, maxDepth, playerId); - logger.fine("simulating post combat actions ----------------------------------------------------------------------------------------"); + logger.debug("simulating post combat actions ----------------------------------------------------------------------------------------"); addActionsTimed(new FilterAbility()); if (root.children.size() > 0) { root = root.children.get(0); @@ -192,20 +189,20 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { Game game = node.getGame(); if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } if (depth <= 0 || SimulationNode.nodeCount > maxNodes || game.isGameOver()) { - logger.fine("simulating -- reached end state"); + logger.debug("simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } else if (node.getChildren().size() > 0) { - logger.fine("simulating -- somthing added children:" + node.getChildren().size()); + logger.debug("simulating -- somthing added children:" + node.getChildren().size()); val = minimaxAB(node, filter, depth-1, alpha, beta); } else { - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(game.getPlayerList().get()).getName()); + if (logger.isDebugEnabled()) + logger.debug("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(game.getPlayerList().get()).getName()); if (allPassed(game)) { if (!game.getStack().isEmpty()) { resolve(node, depth, game); @@ -219,12 +216,12 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { val = GameStateEvaluator.evaluate(playerId, game); } else if (stepFinished) { - logger.fine("step finished"); + logger.debug("step finished"); int testScore = GameStateEvaluator.evaluate(playerId, game); if (game.getActivePlayerId().equals(playerId)) { if (testScore < currentScore) { // if score at end of step is worse than original score don't check further - logger.fine("simulating -- abandoning check, no immediate benefit"); + logger.debug("simulating -- abandoning check, no immediate benefit"); val = testScore; } else { @@ -249,7 +246,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } } else if (node.getChildren().size() > 0) { - logger.fine("simulating -- trigger added children:" + node.getChildren().size()); + logger.debug("simulating -- trigger added children:" + node.getChildren().size()); val = minimaxAB(node, filter, depth, alpha, beta); } else { @@ -257,8 +254,8 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } } - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } @@ -267,7 +264,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { Integer val = null; if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } if (game.getTurn().getStepType() != PhaseStep.DECLARE_BLOCKERS) { @@ -303,8 +300,8 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } if (val == null) val = GameStateEvaluator.evaluate(playerId, game); - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- combat score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- combat score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } @@ -312,7 +309,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { protected int simulateAttackers(Game game, SimulationNode node, UUID attackerId, int depth, int alpha, int beta, boolean counter) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; @@ -321,7 +318,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { for (Combat engagement: attacker.addAttackers(game)) { if (alpha >= beta) { - logger.fine("simulating -- pruning attackers"); + logger.debug("simulating -- pruning attackers"); break; } Game sim = game.copy(); @@ -333,12 +330,12 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, playerId, playerId)); SimulationNode newNode = new SimulationNode(sim, depth, game.getActivePlayerId()); - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating attack -- node#: " + SimulationNode.getCount()); + if (logger.isDebugEnabled()) + logger.debug("simulating attack -- node#: " + SimulationNode.getCount()); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); - logger.fine("resolving triggered abilities"); + logger.debug("resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); @@ -366,15 +363,15 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { node.children.clear(); node.children.add(bestNode); } - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- combat attacker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- combat attacker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } protected int simulateBlockers(Game game, SimulationNode node, UUID defenderId, int depth, int alpha, int beta, boolean counter) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; @@ -384,24 +381,26 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { SimulatedPlayer defender = (SimulatedPlayer) game.getPlayer(defenderId); for (Combat engagement: defender.addBlockers(game)) { if (alpha >= beta) { - logger.fine("simulating -- pruning blockers"); + logger.debug("simulating -- pruning blockers"); break; } Game sim = game.copy(); for (CombatGroup group: engagement.getGroups()) { - UUID attackerId = group.getAttackers().get(0); - for (UUID blockerId: group.getBlockers()) { - sim.getPlayer(defenderId).declareBlocker(blockerId, attackerId, sim); + if (group.getAttackers().size() > 0) { + UUID attackerId = group.getAttackers().get(0); + for (UUID blockerId: group.getBlockers()) { + sim.getPlayer(defenderId).declareBlocker(blockerId, attackerId, sim); + } } } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); SimulationNode newNode = new SimulationNode(sim, depth, defenderId); - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating block -- node#: " + SimulationNode.getCount()); + if (logger.isDebugEnabled()) + logger.debug("simulating block -- node#: " + SimulationNode.getCount()); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); - logger.fine("resolving triggered abilities"); + logger.debug("resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); @@ -437,20 +436,20 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { node.children.clear(); node.children.add(bestNode); } - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- combat blocker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- combat blocker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } protected int simulateCounterAttack(Game game, SimulationNode node, int depth, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; if (!game.isGameOver()) { - logger.fine("simulating -- counter attack"); + logger.debug("simulating -- counter attack"); simulateToEnd(game); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getTurn().setPhase(new BeginningPhase()); @@ -461,8 +460,8 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { game.getPhase().endPhase(game, game.getActivePlayerId()); } val = simulateCombat(game, node, depth-1, alpha, beta, true); - if (logger.isLoggable(Level.FINE)) - logger.fine("returning -- counter attack score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + if (logger.isDebugEnabled()) + logger.debug("returning -- counter attack score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } if (val == null) val = GameStateEvaluator.evaluate(playerId, game); @@ -472,7 +471,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { protected void simulateStep(Game game, Step step) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return; } if (!game.isGameOver()) { @@ -492,7 +491,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { protected void finishCombat(Game game) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return; } simulateStep(game, new CombatDamageStep(true)); @@ -503,10 +502,10 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { protected int simulatePostCombatMain(Game game, SimulationNode node, int depth, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - logger.fine("simulating -- post combat main"); + logger.debug("simulating -- post combat main"); game.getTurn().setPhase(new PostCombatMainPhase()); if (game.getPhase().beginPhase(game, game.getActivePlayerId())) { game.getPhase().setStep(new PostCombatMainStep()); @@ -520,7 +519,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { protected void simulateToEnd(Game game) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.fine("interrupted"); + logger.debug("interrupted"); return; } if (!game.isGameOver()) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/Config.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/Config.java index c0854286f46..a5ad2526d48 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/Config.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/Config.java @@ -33,9 +33,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; -import mage.util.Logging; +import org.apache.log4j.Logger; /** * @@ -43,7 +41,7 @@ import mage.util.Logging; */ public class Config { - private final static Logger logger = Logging.getLogger(Config.class.getName()); + private final static Logger logger = Logger.getLogger(Config.class); public static final int maxDepth; public static final int maxNodes; @@ -59,9 +57,9 @@ public class Config { File file = new File(Config.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); p.load(new FileInputStream(new File(file.getParent() + File.separator + "AIMinimax.properties"))); } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); + logger.fatal("", ex); } catch (URISyntaxException ex) { - Logger.getLogger(Config.class.getName()).log(Level.SEVERE, null, ex); + logger.fatal("", ex); } maxDepth = Integer.parseInt(p.getProperty("maxDepth")); maxNodes = Integer.parseInt(p.getProperty("maxNodes")); diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java index be3b0c762c7..48d4c979036 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java @@ -31,18 +31,11 @@ package mage.player.ai; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; import mage.abilities.Ability; -import mage.abilities.ActivatedAbility; import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; import mage.abilities.mana.ManaOptions; @@ -54,8 +47,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; import mage.target.Target; -import mage.util.Copier; -import mage.util.Logging; +import org.apache.log4j.Logger; /** * @@ -63,7 +55,7 @@ import mage.util.Logging; */ public class SimulatedPlayer extends ComputerPlayer { - private final static transient Logger logger = Logging.getLogger(SimulatedPlayer.class.getName()); + private final static transient Logger logger = Logger.getLogger(SimulatedPlayer.class); private boolean isSimulatedPlayer; private FilterAbility filter; private transient ConcurrentLinkedQueue allActions; @@ -154,10 +146,10 @@ public class SimulatedPlayer extends ComputerPlayer { sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, sim); } if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) { - logger.fine("simulating -- found redundant attack combination"); + logger.debug("simulating -- found redundant attack combination"); } - else if (logger.isLoggable(Level.FINE)) { - logger.fine("simulating -- attack:" + sim.getCombat().getGroups().size()); + else if (logger.isDebugEnabled()) { + logger.debug("simulating -- attack:" + sim.getCombat().getGroups().size()); } } return new ArrayList(engagements.values()); @@ -185,15 +177,15 @@ public class SimulatedPlayer extends ComputerPlayer { int numGroups = game.getCombat().getGroups().size(); //try to block each attacker with each potential blocker Permanent blocker = blockers.get(0); - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- block:" + blocker); + if (logger.isDebugEnabled()) + logger.debug("simulating -- block:" + blocker); List remaining = remove(blockers, blocker); for (int i = 0; i < numGroups; i++) { if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) { Game sim = game.copy(); sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim); if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) - logger.fine("simulating -- found redundant block combination"); + logger.debug("simulating -- found redundant block combination"); addBlocker(sim, remaining, engagements); // and recurse minus the used blocker } } @@ -205,8 +197,8 @@ public class SimulatedPlayer extends ComputerPlayer { Ability ability = source.copy(); List options = getPlayableOptions(ability, game); if (options.size() == 0) { - if (logger.isLoggable(Level.FINE)) - logger.fine("simulating -- triggered ability:" + ability); + if (logger.isDebugEnabled()) + logger.debug("simulating -- triggered ability:" + ability); game.getStack().push(new StackAbility(ability, playerId)); ability.activate(game, false); game.applyEffects(); @@ -216,7 +208,7 @@ public class SimulatedPlayer extends ComputerPlayer { SimulationNode parent = (SimulationNode) game.getCustomData(); int depth = parent.getDepth() - 1; if (depth == 0) return true; - logger.fine("simulating -- triggered ability - adding children:" + options.size()); + logger.debug("simulating -- triggered ability - adding children:" + options.size()); for (Ability option: options) { addAbilityNode(parent, option, depth, game); } @@ -230,7 +222,7 @@ public class SimulatedPlayer extends ComputerPlayer { ability.activate(sim, false); sim.applyEffects(); SimulationNode newNode = new SimulationNode(sim, depth, playerId); - logger.fine("simulating -- node #:" + SimulationNode.getCount() + " triggered ability option"); + logger.debug("simulating -- node #:" + SimulationNode.getCount() + " triggered ability option"); for (Target target: ability.getTargets()) { for (UUID targetId: target.getTargets()) { newNode.getTargets().add(targetId); diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index d3f8d017126..ddb34858b9e 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -33,8 +33,6 @@ import mage.server.util.PluginClassLoader; import java.io.File; import java.io.FilenameFilter; import java.net.InetAddress; -import java.util.logging.Level; -import java.util.logging.Logger; import mage.game.match.MatchType; import mage.game.tournament.TournamentType; import mage.server.game.DeckValidatorFactory; @@ -45,7 +43,7 @@ import mage.server.util.ConfigSettings; import mage.server.util.config.Plugin; import mage.server.util.config.GamePlugin; import mage.util.Copier; -import mage.util.Logging; +import org.apache.log4j.Logger; /** * @@ -53,7 +51,7 @@ import mage.util.Logging; */ public class Main { - private static Logger logger = Logging.getLogger(Main.class.getName()); + private static Logger logger = Logger.getLogger(Main.class); private final static String testModeArg = "-testMode="; private final static String pluginFolder = "plugins"; @@ -68,7 +66,7 @@ public class Main { public static void main(String[] args) { logger.info("Starting MAGE server version " + version); - logger.info("Logging level: " + Logging.getLevel(logger)); + logger.info("Logging level: " + logger.getLevel()); deleteSavedGames(); ConfigSettings config = ConfigSettings.getInstance(); for (GamePlugin plugin: config.getGameTypes()) { @@ -101,7 +99,7 @@ public class Main { ip = InetAddress.getLocalHost().getHostAddress(); } } catch (UnknownHostException ex) { - logger.log(Level.WARNING, "Could not get server address: ", ex); + logger.warn("Could not get server address: ", ex); } String ipParam = System.getProperty("server"); if (ipParam != null) { @@ -117,9 +115,9 @@ public class Main { logger.info("Loading plugin: " + plugin.getClassName()); return Class.forName(plugin.getClassName(), true, classLoader); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Plugin not Found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Plugin not Found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading plugin " + plugin.getJar(), ex); + logger.fatal("Error loading plugin " + plugin.getJar(), ex); } return null; } @@ -130,9 +128,9 @@ public class Main { logger.info("Loading game type: " + plugin.getClassName()); return (MatchType) Class.forName(plugin.getTypeName(), true, classLoader).newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Game type not found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Game type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading game type " + plugin.getJar(), ex); + logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } @@ -143,9 +141,9 @@ public class Main { logger.info("Loading tournament type: " + plugin.getClassName()); return (TournamentType) Class.forName(plugin.getTypeName(), true, classLoader).newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Tournament type not found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Tournament type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading game type " + plugin.getJar(), ex); + logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } 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 157e8001ca9..2a63cfaeded 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 @@ -127,16 +127,16 @@ public class PlayGameTest extends MageTestBase { if (card != null) { cards.add(card); } else { - logger.severe("Couldn't find a card: " + cardName); - logger.severe("line: " + line); + logger.fatal("Couldn't find a card: " + cardName); + logger.fatal("line: " + line); } } } } else { - logger.warning("Unknown player: " + nickname); + logger.warn("Unknown player: " + nickname); } } else { - logger.warning("Init string wasn't parsed: " + line); + logger.warn("Init string wasn't parsed: " + line); } } } finally { 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 c9a0700086e..2d4d6f4235f 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 @@ -11,20 +11,19 @@ import mage.server.util.PluginClassLoader; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; import mage.util.Copier; -import mage.util.Logging; import org.junit.BeforeClass; import java.io.File; import java.io.FilenameFilter; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import java.util.regex.Pattern; /** * @author ayratn */ public class MageTestBase { - protected static Logger logger = Logging.getLogger(MageTestBase.class.getName()); + protected static Logger logger = Logger.getLogger(MageTestBase.class); public static PluginClassLoader classLoader = new PluginClassLoader(); @@ -34,8 +33,9 @@ public class MageTestBase { @BeforeClass public static void init() { + Logger.getRootLogger().setLevel(Level.DEBUG); logger.info("Starting MAGE tests"); - logger.info("Logging level: " + Logging.getLevel(logger)); + logger.info("Logging level: " + logger.getLevel()); deleteSavedGames(); ConfigSettings config = ConfigSettings.getInstance(); for (GamePlugin plugin : config.getGameTypes()) { @@ -59,9 +59,9 @@ public class MageTestBase { logger.info("Loading plugin: " + plugin.getClassName()); return Class.forName(plugin.getClassName(), true, classLoader); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Plugin not Found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Plugin not Found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading plugin " + plugin.getJar(), ex); + logger.fatal("Error loading plugin " + plugin.getJar(), ex); } return null; } @@ -72,9 +72,9 @@ public class MageTestBase { logger.info("Loading game type: " + plugin.getClassName()); return (MatchType) Class.forName(plugin.getTypeName(), true, classLoader).newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Game type not found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Game type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading game type " + plugin.getJar(), ex); + logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } @@ -85,9 +85,9 @@ public class MageTestBase { logger.info("Loading tournament type: " + plugin.getClassName()); return (TournamentType) Class.forName(plugin.getTypeName(), true, classLoader).newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "Tournament type not found:" + plugin.getJar() + " - check plugin folder"); + logger.warn("Tournament type not found:" + plugin.getJar() + " - check plugin folder"); } catch (Exception ex) { - logger.log(Level.SEVERE, "Error loading game type " + plugin.getJar(), ex); + logger.fatal("Error loading game type " + plugin.getJar(), ex); } return null; } diff --git a/Mage/src/mage/target/common/TargetCreatureOrPlayer.java b/Mage/src/mage/target/common/TargetCreatureOrPlayer.java index 00fa22362d5..033bd88d5ce 100644 --- a/Mage/src/mage/target/common/TargetCreatureOrPlayer.java +++ b/Mage/src/mage/target/common/TargetCreatureOrPlayer.java @@ -90,13 +90,18 @@ public class TargetCreatureOrPlayer extends TargetImpl { @Override public boolean canTarget(UUID id, Ability source, Game game) { + return canTarget(null, id, source, game); + } + + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); MageObject targetSource = game.getObject(source.getSourceId()); if (permanent != null) { if (source != null) - return permanent.canBeTargetedBy(targetSource) && filter.match(permanent, source.getControllerId(), game); + //TODO: check for replacement effects + return permanent.canBeTargetedBy(game.getObject(source.getSourceId())) && filter.match(permanent, controllerId, game); else - return filter.match(permanent); + return filter.match(permanent, controllerId, game); } Player player = game.getPlayer(id); if (player != null)