diff --git a/src/magic/DeckStrCal.java b/src/magic/DeckStrCal.java index c700e57f83..d4c3b7ba36 100644 --- a/src/magic/DeckStrCal.java +++ b/src/magic/DeckStrCal.java @@ -1,5 +1,6 @@ package magic; +import magic.headless.HeadlessGameController; import magic.ai.MagicAI; import magic.ai.MagicAIImpl; import magic.data.DeckUtils; @@ -8,10 +9,10 @@ import magic.model.MagicDuel; import magic.model.MagicGame; import magic.model.MagicGameLog; import magic.model.MagicRandom; -import magic.ui.SwingGameController; - import java.io.File; +import magic.data.CardDefinitions; import magic.exception.handler.ConsoleExceptionHandler; +import magic.utility.ProgressReporter; public class DeckStrCal { @@ -163,9 +164,7 @@ public class DeckStrCal { System.exit(1); } - // Load cards and cubes. - MagicMain.initializeEngine(); - MagicGameLog.initialize(); + initialize(); for (int i = 0; i < repeat; i++) { runDuel(); @@ -193,7 +192,8 @@ public class DeckStrCal { while (testDuel.getGamesPlayed() < testDuel.getGamesTotal()) { final MagicGame game=testDuel.nextGame(); game.setArtificial(true); - final SwingGameController controller=new SwingGameController(game); + + final HeadlessGameController controller = new HeadlessGameController(game); //maximum duration of a game is 60 minutes controller.setMaxTestGameDuration(3600000); @@ -226,4 +226,9 @@ public class DeckStrCal { (testDuel.getGamesPlayed() - testDuel.getGamesWon()) ); } + + private static void initialize() { + CardDefinitions.loadCardDefinitions(new ProgressReporter()); + MagicGameLog.initialize(); + } } diff --git a/src/magic/MagicMain.java b/src/magic/MagicMain.java index a2be9c4ae6..f7d14c0cad 100644 --- a/src/magic/MagicMain.java +++ b/src/magic/MagicMain.java @@ -137,7 +137,7 @@ public class MagicMain { } } - static void initializeEngine() { + private static void initializeEngine() { if (Boolean.getBoolean("parseMissing")) { UnimplementedParser.parseScriptsMissing(reporter); reporter.setMessage("Parsing card abilities..."); diff --git a/src/magic/headless/HeadlessGameController.java b/src/magic/headless/HeadlessGameController.java new file mode 100644 index 0000000000..7e96de4fd1 --- /dev/null +++ b/src/magic/headless/HeadlessGameController.java @@ -0,0 +1,294 @@ +package magic.headless; + +import magic.exception.UndoClickedException; +import magic.ai.MagicAI; +import magic.model.ILogBookListener; +import magic.model.MagicCardList; +import magic.model.MagicGame; +import magic.model.MagicLogBookEvent; +import magic.model.MagicPlayer; +import magic.model.MagicSource; +import magic.model.event.MagicEvent; +import magic.model.event.MagicPriorityEvent; +import magic.model.target.MagicTarget; +import magic.model.target.MagicTargetNone; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import magic.model.IGameController; +import magic.model.MagicColor; +import magic.model.MagicManaCost; +import magic.model.MagicSubType; +import magic.model.choice.MagicPlayChoiceResult; + +public class HeadlessGameController implements IGameController, ILogBookListener { + + private long MAX_TEST_MODE_DURATION=10000; + + private final MagicGame game; + // isDeckStrMode is true when game is run via DeckStrengthViewer or DeckStrCal. + private final boolean isDeckStrMode; + private final AtomicBoolean running = new AtomicBoolean(false); + private final AtomicBoolean isPaused = new AtomicBoolean(false); + private boolean actionClicked; + private boolean resetGame; + private MagicTarget choiceClicked = MagicTargetNone.getInstance(); + private final BlockingQueue input = new SynchronousQueue<>(); + + /** Fully artificial test game. */ + public HeadlessGameController(final MagicGame aGame) { + game = aGame; + isDeckStrMode = true; + } + + @Override + public void enableForwardButton() {} + + @Override + public void disableActionButton(final boolean thinking) {} + + @Override + public void pause(final int t) { + try { //sleep + Thread.sleep(t); + } catch (final InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + /** Returns true when undo was clicked. */ + private boolean waitForInputOrUndo() { + try { + return input.take(); + } catch (final InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void waitForInput() throws UndoClickedException { + try { + final boolean undoClicked = input.take(); + if (undoClicked) { + throw new UndoClickedException(); + } + } catch (final InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + private void resume(final boolean undoClicked) { + input.offer(undoClicked); + } + + private void undoClicked() { + if (game.hasUndoPoints()) { + actionClicked = false; + choiceClicked = MagicTargetNone.getInstance(); + setSourceCardDefinition(MagicEvent.NO_SOURCE); + clearValidChoices(); + resume(true); + } + } + + @Override + public boolean isActionClicked() { + return actionClicked; + } + + @SuppressWarnings("unchecked") + @Override + public T getChoiceClicked() { + return (T)choiceClicked; + } + + public void setMaxTestGameDuration(final long duration) { + MAX_TEST_MODE_DURATION = duration; + } + + @Override + public void setSourceCardDefinition(final MagicSource source) {} + + @Override + public void focusViewers(final int handGraveyard) {} + + @Override + public void clearCards() { + showCards(new MagicCardList()); + } + + @Override + public void showCards(final MagicCardList cards) {} + + @Override + public void clearValidChoices() {} + + @Override + public void setValidChoices(final Set aValidChoices,final boolean aCombatChoice) {} + + /** + * Update/render the gui based on the model state. + */ + @Override + public void updateGameView() { } + + @Override + public void showMessage(final MagicSource source, final String message) { + System.out.println(message); + } + + private Object[] getArtificialNextEventChoiceResults(final MagicEvent event) { + if (!isDeckStrMode) { + disableActionButton(true); + showMessage(event.getSource(),event.getChoiceDescription()); + } + + //dynamically get the AI based on the player's index + final MagicPlayer player = event.getPlayer(); + final MagicAI ai = game.getDuel().getAIs()[player.getIndex()]; + return ai.findNextEventChoiceResults(game, player); + } + + private void executeNextEventWithChoices(final MagicEvent event) { + game.executeNextEvent(getArtificialNextEventChoiceResults(event)); + } + + private void performUndo() { + if (resetGame) { + resetGame=false; + while (game.hasUndoPoints()) { + game.restore(); + } + } else { + game.restore(); + } + } + + /** + * Main game loop runs on separate thread. + */ + public void runGame() { + final long startTime=System.currentTimeMillis(); + running.set(true); + while (running.get()) { + if (isPaused.get()) { + pause(100); + } else if (game.isFinished()) { + doNextActionOnGameFinished(); + } else { + executeNextEventOrPhase(); + if (isDeckStrMode) { + if (System.currentTimeMillis() - startTime > MAX_TEST_MODE_DURATION) { + System.err.println("WARNING. Max time for AI game exceeded"); + running.set(false); + } + } else { + updateGameView(); + } + } + } + } + + /** + * Once a game has finished determine what happens next. + *

+ * If running an automated game then automatically start next game/duel. + * If an interactive game then wait for input from user. + */ + private void doNextActionOnGameFinished() { + if (isDeckStrMode) { + game.advanceDuel(false); + running.set(false); + } else { + game.logMessages(); + clearValidChoices(); + game.advanceDuel(false); + running.set(false); + } + } + + private void executeNextEventOrPhase() { + if (game.hasNextEvent()) { + executeNextEvent(); + } else { + game.executePhase(); + } + } + + private void executeNextEvent() { + final MagicEvent event=game.getNextEvent(); + if (event instanceof MagicPriorityEvent) { + game.logMessages(); + } + if (event.hasChoice()) { + executeNextEventWithChoices(event); + } else { + game.executeNextEvent(); + } + } + + @Override + public void messageLogged(final MagicLogBookEvent ev) {} + + @Override + public MagicSubType getLandSubTypeChoice(final MagicSource source) throws UndoClickedException { + return null; + } + + @Override + public boolean getPayBuyBackCostChoice(final MagicSource source, final String costText) throws UndoClickedException { + return false; + } + + @Override + public MagicColor getColorChoice(final MagicSource source) throws UndoClickedException { + return null; + } + + @Override + public int getMultiKickerCountChoice( + final MagicSource source, + final MagicManaCost cost, + final int maximumCount, + final String name) throws UndoClickedException { + return 0; + } + + @Override + public int getSingleKickerCountChoice( + final MagicSource source, + final MagicManaCost cost, + final String name) throws UndoClickedException { + return 0; + } + + @Override + public boolean getMayChoice(final MagicSource source, final String description) throws UndoClickedException { + return false; + } + + @Override + public boolean getTakeMulliganChoice( + final MagicSource source, + final MagicPlayer player) throws UndoClickedException { + return false; + } + + @Override + public int getModeChoice(final MagicSource source, final List availableModes) throws UndoClickedException { + return 0; + } + + @Override + public int getPayManaCostXChoice(final MagicSource source, final int maximumX) throws UndoClickedException { + return 0; + } + + @Override + public MagicPlayChoiceResult getPlayChoice(final MagicSource source, final List results) throws UndoClickedException { + return null; + } + +}