package net.demilich.metastone.game; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Stack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.demilich.metastone.game.actions.ActionType; import net.demilich.metastone.game.actions.GameAction; import net.demilich.metastone.game.cards.Card; import net.demilich.metastone.game.cards.CardCatalogue; import net.demilich.metastone.game.cards.CardCollection; import net.demilich.metastone.game.cards.costmodifier.CardCostModifier; import net.demilich.metastone.game.decks.DeckFormat; import net.demilich.metastone.game.entities.Entity; import net.demilich.metastone.game.entities.minions.Summon; import net.demilich.metastone.game.events.GameEvent; import net.demilich.metastone.game.logic.GameLogic; import net.demilich.metastone.game.logic.MatchResult; import net.demilich.metastone.game.logic.TargetLogic; import net.demilich.metastone.game.spells.trigger.IGameEventListener; import net.demilich.metastone.game.spells.trigger.TriggerManager; import net.demilich.metastone.game.targeting.CardReference; import net.demilich.metastone.game.targeting.EntityReference; import net.demilich.metastone.utils.IDisposable; public class GameContext implements Cloneable, IDisposable { public static final int PLAYER_1 = 0; public static final int PLAYER_2 = 1; private static final Logger logger = LoggerFactory.getLogger(GameContext.class); private final Player[] players = new Player[2]; private final GameLogic logic; private final DeckFormat deckFormat; private final TargetLogic targetLogic = new TargetLogic(); private TriggerManager triggerManager = new TriggerManager(); private final HashMap<Environment, Object> environment = new HashMap<>(); private final List<CardCostModifier> cardCostModifiers = new ArrayList<>(); protected int activePlayer = -1; private Player winner; private MatchResult result; private TurnState turnState = TurnState.TURN_ENDED; private int turn; private int actionsThisTurn; private boolean ignoreEvents; private CardCollection tempCards = new CardCollection(); public GameContext(Player player1, Player player2, GameLogic logic, DeckFormat deckFormat) { this.getPlayers()[PLAYER_1] = player1; player1.setId(PLAYER_1); this.getPlayers()[PLAYER_2] = player2; player2.setId(PLAYER_2); this.logic = logic; this.deckFormat = deckFormat; this.logic.setContext(this); tempCards.removeAll(); } protected boolean acceptAction(GameAction nextAction) { return true; } public void addCardCostModifier(CardCostModifier cardCostModifier) { getCardCostModifiers().add(cardCostModifier); } public void addTempCard(Card card) { tempCards.add(card); } public void addTrigger(IGameEventListener trigger) { triggerManager.addTrigger(trigger); } @Override public GameContext clone() { GameLogic logicClone = getLogic().clone(); Player player1Clone = getPlayer1().clone(); // player1Clone.getDeck().shuffle(); Player player2Clone = getPlayer2().clone(); // player2Clone.getDeck().shuffle(); GameContext clone = new GameContext(player1Clone, player2Clone, logicClone, deckFormat); clone.tempCards = tempCards.clone(); clone.triggerManager = triggerManager.clone(); clone.activePlayer = activePlayer; clone.turn = turn; clone.actionsThisTurn = actionsThisTurn; clone.result = result; clone.turnState = turnState; clone.winner = logicClone.getWinner(player1Clone, player2Clone); clone.cardCostModifiers.clear(); for (CardCostModifier cardCostModifier : cardCostModifiers) { clone.cardCostModifiers.add(cardCostModifier.clone()); } Stack<Integer> damageStack = new Stack<Integer>(); damageStack.addAll(getDamageStack()); clone.getEnvironment().put(Environment.DAMAGE_STACK, damageStack); Stack<EntityReference> summonReferenceStack = new Stack<EntityReference>(); summonReferenceStack.addAll(getSummonReferenceStack()); clone.getEnvironment().put(Environment.SUMMON_REFERENCE_STACK, summonReferenceStack); Stack<EntityReference> eventTargetReferenceStack = new Stack<EntityReference>(); eventTargetReferenceStack.addAll(getEventTargetStack()); clone.getEnvironment().put(Environment.EVENT_TARGET_REFERENCE_STACK, eventTargetReferenceStack); for (Environment key : getEnvironment().keySet()) { if (!key.customClone()) { clone.getEnvironment().put(key, getEnvironment().get(key)); } } clone.getLogic().setLoggingEnabled(false); return clone; } @Override public void dispose() { for (int i = 0; i < players.length; i++) { players[i] = null; } getCardCostModifiers().clear(); triggerManager.dispose(); environment.clear(); } private void endGame() { winner = logic.getWinner(getActivePlayer(), getOpponent(getActivePlayer())); for (Player player : getPlayers()) { player.getBehaviour().onGameOver(this, player.getId(), winner != null ? winner.getId() : -1); } if (winner != null) { logger.debug("Game finished after " + turn + " turns, the winner is: " + winner.getName()); winner.getStatistics().gameWon(); Player looser = getOpponent(winner); looser.getStatistics().gameLost(); } else { logger.debug("Game finished after " + turn + " turns, DRAW"); getPlayer1().getStatistics().gameLost(); getPlayer2().getStatistics().gameLost(); } } public void endTurn() { logic.endTurn(activePlayer); activePlayer = activePlayer == PLAYER_1 ? PLAYER_2 : PLAYER_1; onGameStateChanged(); turnState = TurnState.TURN_ENDED; } private Card findCardinCollection(CardCollection cardCollection, int cardId) { for (Card card : cardCollection) { if (card.getId() == cardId) { return card; } } return null; } public void fireGameEvent(GameEvent gameEvent) { if (ignoreEvents()) { return; } try { triggerManager.fireGameEvent(gameEvent); } catch(Exception e) { logger.error("Error while processing gameEvent {}", gameEvent); logic.panicDump(); throw e; } } public boolean gameDecided() { result = logic.getMatchResult(getActivePlayer(), getOpponent(getActivePlayer())); winner = logic.getWinner(getActivePlayer(), getOpponent(getActivePlayer())); return result != MatchResult.RUNNING; } public Player getActivePlayer() { return getPlayer(activePlayer); } public int getActivePlayerId() { return activePlayer; } public List<Summon> getAdjacentSummons(Player player, EntityReference minionReference) { List<Summon> adjacentSummons = new ArrayList<>(); Summon summon = (Summon) resolveSingleTarget(minionReference); List<Summon> summons = getPlayer(summon.getOwner()).getSummons(); int index = summons.indexOf(summon); if (index == -1) { return adjacentSummons; } int left = index - 1; int right = index + 1; if (left > -1 && left < summons.size()) { adjacentSummons.add(summons.get(left)); } if (right > -1 && right < summons.size()) { adjacentSummons.add(summons.get(right)); } return adjacentSummons; } public GameAction getAutoHeroPowerAction() { return logic.getAutoHeroPowerAction(activePlayer); } public int getBoardPosition(Summon summon) { for (Player player : getPlayers()) { List<Summon> summons = player.getSummons(); for (int i = 0; i < summons.size(); i++) { if (summons.get(i) == summon) { return i; } } } return -1; } public Card getCardById(String cardId) { Card card = CardCatalogue.getCardById(cardId); if (card == null) { for (Card tempCard : tempCards) { if (tempCard.getCardId().equalsIgnoreCase(cardId)) { return tempCard.clone(); } } } return card; } public List<CardCostModifier> getCardCostModifiers() { return cardCostModifiers; } @SuppressWarnings("unchecked") public Stack<Integer> getDamageStack() { if (!environment.containsKey(Environment.DAMAGE_STACK)) { environment.put(Environment.DAMAGE_STACK, new Stack<Integer>()); } return (Stack<Integer>) environment.get(Environment.DAMAGE_STACK); } public DeckFormat getDeckFormat() { return deckFormat; } public HashMap<Environment, Object> getEnvironment() { return environment; } public Card getEventCard() { return (Card) resolveSingleTarget((EntityReference) getEnvironment().get(Environment.EVENT_CARD)); } @SuppressWarnings("unchecked") public Stack<EntityReference> getEventTargetStack() { if (!environment.containsKey(Environment.EVENT_TARGET_REFERENCE_STACK)) { environment.put(Environment.EVENT_TARGET_REFERENCE_STACK, new Stack<EntityReference>()); } return (Stack<EntityReference>) environment.get(Environment.EVENT_TARGET_REFERENCE_STACK); } public List<Summon> getLeftSummons(Player player, EntityReference minionReference) { List<Summon> leftSummons = new ArrayList<>(); Summon summon = (Summon) resolveSingleTarget(minionReference); List<Summon> summons = getPlayer(summon.getOwner()).getSummons(); int index = summons.indexOf(summon); if (index == -1) { return leftSummons; } for (int i = 0; i < index; i++) { leftSummons.add(summons.get(i)); } return leftSummons; } public GameLogic getLogic() { return logic; } public int getMinionCount(Player player) { return player.getMinions().size(); } public int getSummonCount(Player player) { return player.getSummons().size(); } public Player getOpponent(Player player) { return player.getId() == PLAYER_1 ? getPlayer2() : getPlayer1(); } public List<Summon> getOppositeSummons(Player player, EntityReference minionReference) { List<Summon> oppositeSummons = new ArrayList<>(); Summon summon = (Summon) resolveSingleTarget(minionReference); Player owner = getPlayer(summon.getOwner()); Player opposingPlayer = getOpponent(owner); int index = owner.getSummons().indexOf(summon); if (opposingPlayer.getSummons().size() == 0 || owner.getSummons().size() == 0 || index == -1) { return oppositeSummons; } List<Summon> opposingSummons = opposingPlayer.getSummons(); int delta = opposingPlayer.getSummons().size() - owner.getSummons().size(); if (delta % 2 == 0) { delta /= 2; int epsilon = delta + index; if (epsilon > -1 && epsilon < opposingSummons.size()) { oppositeSummons.add(opposingSummons.get(epsilon)); } } else { delta = (delta - 1) / 2; int epsilon = delta + index; if (epsilon > -1 && epsilon < opposingSummons.size()) { oppositeSummons.add(opposingSummons.get(epsilon)); } if (epsilon + 1 > -1 && epsilon + 1 < opposingSummons.size()) { oppositeSummons.add(opposingSummons.get(epsilon + 1)); } } return oppositeSummons; } public Card getPendingCard() { return (Card) resolveSingleTarget((EntityReference) getEnvironment().get(Environment.PENDING_CARD)); } public Player getPlayer(int index) { return players[index]; } public Player getPlayer1() { return getPlayers()[PLAYER_1]; } public Player getPlayer2() { return getPlayers()[PLAYER_2]; } public Player[] getPlayers() { return players; } public List<Summon> getRightSummons(Player player, EntityReference minionReference) { List<Summon> rightSummons = new ArrayList<>(); Summon summon = (Summon) resolveSingleTarget(minionReference); List<Summon> summons = getPlayer(summon.getOwner()).getSummons(); int index = summons.indexOf(summon); if (index == -1) { return rightSummons; } for (int i = index + 1; i < player.getSummons().size(); i++) { rightSummons.add(summons.get(i)); } return rightSummons; } @SuppressWarnings("unchecked") public Stack<EntityReference> getSummonReferenceStack() { if (!environment.containsKey(Environment.SUMMON_REFERENCE_STACK)) { environment.put(Environment.SUMMON_REFERENCE_STACK, new Stack<EntityReference>()); } return (Stack<EntityReference>) environment.get(Environment.SUMMON_REFERENCE_STACK); } public CardCollection getTempCards() { return tempCards; } public int getTotalMinionCount() { int totalMinionCount = 0; for (int i = 0; i < players.length; i++) { totalMinionCount += getMinionCount(players[i]); } return totalMinionCount; } public int getTotalSummonCount() { int totalSummonCount = 0; for (int i = 0; i < players.length; i++) { totalSummonCount += getSummonCount(players[i]); } return totalSummonCount; } public List<IGameEventListener> getTriggersAssociatedWith(EntityReference entityReference) { return triggerManager.getTriggersAssociatedWith(entityReference); } public int getTurn() { return turn; } public TurnState getTurnState() { return turnState; } public List<GameAction> getValidActions() { if (gameDecided()) { return new ArrayList<>(); } return logic.getValidActions(activePlayer); } public int getWinningPlayerId() { return winner == null ? -1 : winner.getId(); } public boolean hasAutoHeroPower() { if (gameDecided()) { return false; } return logic.hasAutoHeroPower(activePlayer); } public boolean ignoreEvents() { return ignoreEvents; } public void init() { int startingPlayerId = logic.determineBeginner(PLAYER_1, PLAYER_2); activePlayer = getPlayer(startingPlayerId).getId(); logger.debug(getActivePlayer().getName() + " begins"); logic.init(activePlayer, true); logic.init(getOpponent(getActivePlayer()).getId(), false); } protected void onGameStateChanged() { } private void performAction(int playerId, GameAction gameAction) { logic.performGameAction(playerId, gameAction); onGameStateChanged(); } public void play() { logger.debug("Game starts: " + getPlayer1().getName() + " VS. " + getPlayer2().getName()); init(); while (!gameDecided()) { startTurn(activePlayer); while (playTurn()) {} if (getTurn() > GameLogic.TURN_LIMIT) { break; } } endGame(); } public void playFromState(){ //Play the whole game starting from any turn while (!gameDecided()) { startTurn(getActivePlayer().getId()); while (playTurn()) {} if (getTurn() > GameLogic.TURN_LIMIT) { break; } } endGame(); } public boolean playTurn() { if (++actionsThisTurn > 99) { logger.warn("Turn has been forcefully ended after {} actions", actionsThisTurn); endTurn(); return false; } if (logic.hasAutoHeroPower(activePlayer)) { performAction(activePlayer, getAutoHeroPowerAction()); return true; } List<GameAction> validActions = getValidActions(); if (validActions.size() == 0) { //endTurn(); return false; } GameAction nextAction = getActivePlayer().getBehaviour().requestAction(this, getActivePlayer(), getValidActions()); while (!acceptAction(nextAction)) { nextAction = getActivePlayer().getBehaviour().requestAction(this, getActivePlayer(), getValidActions()); } if (nextAction == null) { throw new RuntimeException("Behaviour " + getActivePlayer().getBehaviour().getName() + " selected NULL action while " + getValidActions().size() + " actions were available"); } performAction(activePlayer, nextAction); return nextAction.getActionType() != ActionType.END_TURN; } public void printCurrentTriggers() { logger.info("Active spelltriggers:"); triggerManager.printCurrentTriggers(); } public void removeTrigger(IGameEventListener trigger) { triggerManager.removeTrigger(trigger); } public void removeTriggersAssociatedWith(EntityReference entityReference, boolean removeAuras) { triggerManager.removeTriggersAssociatedWith(entityReference, removeAuras); } public Card resolveCardReference(CardReference cardReference) { Player player = getPlayer(cardReference.getPlayerId()); if (getPendingCard() != null && getPendingCard().getCardReference().equals(cardReference)) { return getPendingCard(); } switch (cardReference.getLocation()) { case DECK: return findCardinCollection(player.getDeck(), cardReference.getCardId()); case HAND: return findCardinCollection(player.getHand(), cardReference.getCardId()); case PENDING: return getPendingCard(); case HERO_POWER: return player.getHero().getHeroPower(); default: break; } logger.error("Could not resolve cardReference {}", cardReference); new RuntimeException().printStackTrace(); return null; } public Entity resolveSingleTarget(EntityReference targetKey) { if (targetKey == null) { return null; } return targetLogic.findEntity(this, targetKey); } public List<Entity> resolveTarget(Player player, Entity source, EntityReference targetKey) { return targetLogic.resolveTargetKey(this, player, source, targetKey); } public void setEventCard(Card eventCard) { if (eventCard != null) { getEnvironment().put(Environment.EVENT_CARD, eventCard.getReference()); } else { getEnvironment().put(Environment.EVENT_CARD, null); } } public void setIgnoreEvents(boolean ignoreEvents) { this.ignoreEvents = ignoreEvents; } public void setPendingCard(Card pendingCard) { if (pendingCard != null) { getEnvironment().put(Environment.PENDING_CARD, pendingCard.getReference()); } else { getEnvironment().put(Environment.PENDING_CARD, null); } } protected void startTurn(int playerId) { turn++; logic.startTurn(playerId); onGameStateChanged(); actionsThisTurn = 0; turnState = TurnState.TURN_IN_PROGRESS; } @Override public String toString() { StringBuilder builder = new StringBuilder("GameContext hashCode: " + hashCode() + "\nPlayer: "); for (Player player : players) { builder.append(player.getName()); builder.append(" Mana: "); builder.append(player.getMana()); builder.append('/'); builder.append(player.getMaxMana()); builder.append(" HP: "); builder.append(player.getHero().getHp() + "(" + player.getHero().getArmor() + ")"); builder.append('\n'); builder.append("Behaviour: " + player.getBehaviour().getName() + "\n"); builder.append("Minions:\n"); for (Summon summon : player.getSummons()) { builder.append('\t'); builder.append(summon); builder.append('\n'); } builder.append("Cards (hand):\n"); for (Card card : player.getHand()) { builder.append('\t'); builder.append(card); builder.append('\n'); } builder.append("Secrets:\n"); for (String secretId : player.getSecrets()) { builder.append('\t'); builder.append(secretId); builder.append('\n'); } } builder.append("Turn: " + getTurn() + "\n"); builder.append("Result: " + result + "\n"); builder.append("Winner: " + (winner == null ? "tbd" : winner.getName())); return builder.toString(); } public Entity tryFind(EntityReference targetKey) { try { return resolveSingleTarget(targetKey); } catch (Exception e) { } return null; } }