package com.hearthsim.util; import com.hearthsim.Game; import com.hearthsim.card.Card; import com.hearthsim.card.CharacterIndex; import com.hearthsim.card.Deck; import com.hearthsim.card.minion.Hero; import com.hearthsim.card.minion.Minion; import com.hearthsim.exception.HSException; import com.hearthsim.model.PlayerModel; import com.hearthsim.model.PlayerSide; import com.hearthsim.util.tree.HearthTreeNode; import org.json.JSONObject; /** * A class representing an action that a player can take * * */ public class HearthAction { // TODO the DO_NOT_ verbs are used for history tracking but we can probably optimize them away in the future. public enum Verb { USE_CARD, HERO_ABILITY, ATTACK, UNTARGETABLE_BATTLECRY, TARGETABLE_BATTLECRY, START_TURN, END_TURN, DO_NOT_USE_CARD, DO_NOT_ATTACK, DO_NOT_USE_HEROPOWER, RNG, DRAW_CARDS } public final Verb verb_; private final PlayerSide actionPerformerPlayerSide; private final int cardOrCharacterIndex_; private final PlayerSide targetPlayerSide; public final CharacterIndex targetCharacterIndex; public HearthAction(Verb verb) { this(verb, PlayerSide.CURRENT_PLAYER, -1, null, CharacterIndex.UNKNOWN); } public HearthAction(Verb verb, PlayerSide actionPerformerPlayerSide, int cardOrCharacterIndex) { this(verb, actionPerformerPlayerSide, cardOrCharacterIndex, null, CharacterIndex.UNKNOWN); } public HearthAction(Verb verb, PlayerSide actionPerformerPlayerSide, int cardOrCharacterIndex, PlayerSide targetPlayerSide, CharacterIndex targetCharacterIndex) { verb_ = verb; this.actionPerformerPlayerSide = actionPerformerPlayerSide; cardOrCharacterIndex_ = cardOrCharacterIndex; this.targetPlayerSide = targetPlayerSide; this.targetCharacterIndex = targetCharacterIndex; } public JSONObject toJSON() { JSONObject json = new JSONObject(); json.put("verb_", verb_); json.put("actionPerformerPlayerSide", actionPerformerPlayerSide); json.put("cardOrCharacterIndex_", cardOrCharacterIndex_); json.put("targetPlayerSide", targetPlayerSide); json.put("targetCharacterIndex", targetCharacterIndex); return json; } @Deprecated public HearthTreeNode perform(HearthTreeNode boardState, Deck deckPlayer0, Deck deckPlayer1) throws HSException { return this.perform(boardState); } public HearthTreeNode perform(HearthTreeNode boardState) throws HSException { HearthTreeNode toRet = boardState; PlayerModel actingPlayer = actionPerformerPlayerSide != null ? boardState.data_.modelForSide(actionPerformerPlayerSide) : null; PlayerModel targetPlayer = targetPlayerSide != null ? boardState.data_.modelForSide(targetPlayerSide) : null; switch(verb_) { case USE_CARD: { Card card = actingPlayer.getHand().get(cardOrCharacterIndex_); toRet = card.useOn(targetPlayerSide, targetCharacterIndex, toRet); } break; case HERO_ABILITY: { Hero hero = actingPlayer.getHero(); Minion target = targetPlayer.getCharacter(targetCharacterIndex); toRet = hero.useHeroAbility(targetPlayerSide, target, toRet); } break; case ATTACK: { Minion attacker = actingPlayer.getCharacter(CharacterIndex.fromInteger(cardOrCharacterIndex_)); toRet = attacker.attack(targetPlayerSide, targetCharacterIndex, toRet); } break; case UNTARGETABLE_BATTLECRY: { Minion minion = actingPlayer.getCharacter(CharacterIndex.fromInteger(cardOrCharacterIndex_)); toRet = minion.useUntargetableBattlecry(targetCharacterIndex, toRet); break; } case TARGETABLE_BATTLECRY: { Minion minion = actingPlayer.getCharacter(CharacterIndex.fromInteger(cardOrCharacterIndex_)); toRet = minion.useTargetableBattlecry(targetPlayerSide, targetCharacterIndex, toRet); break; } case START_TURN: { toRet = new HearthTreeNode(Game.beginTurn(boardState.data_.deepCopy())); break; } case END_TURN: { toRet = new HearthTreeNode(Game.endTurn(boardState.data_.deepCopy()).flipPlayers()); break; } case DO_NOT_USE_CARD: { for (Card c : actingPlayer.getHand()) { c.hasBeenUsed(true); } break; } case DO_NOT_ATTACK: { for (Minion minion : actingPlayer.getMinions()) { minion.hasAttacked(true); } actingPlayer.getHero().hasAttacked(true); break; } case DO_NOT_USE_HEROPOWER: { actingPlayer.getHero().hasBeenUsed(true); break; } case RNG: { // We need to perform the current state again if the children don't exist yet. This can happen in certain replay scenarios. // Do not do this if the previous action was *also* RNG or we will end up in an infinite loop. if (toRet.isLeaf() && boardState.getAction().verb_ != Verb.RNG) { boardState.data_.getCurrentPlayer().addNumCardsUsed((byte)-1); //do not double count toRet = boardState.getAction().perform(boardState); } // RNG has declared this child happened toRet = toRet.getChildren().get(cardOrCharacterIndex_); break; } case DRAW_CARDS: { // Note, this action only supports drawing cards from the deck. Cards like Ysera or Webspinner need to be implemented using RNG children. for (int indx = 0; indx < cardOrCharacterIndex_; ++indx) { actingPlayer.drawNextCardFromDeck(); } break; } } return toRet; } }