@Override
 public int hashCode() {
   int hs = p0_hand_.size();
   if (hs < 0) hs = 0;
   int res = hs + p0_minions_.size() * 10 + p1_minions_.size() * 100;
   res += (p0_mana_ <= 0 ? 0 : (p0_mana_ - 1) * 1000);
   res += ((p0_hero_.getHealth() + p1_hero_.getHealth()) % 100) * 10000;
   int th = 0;
   if (hs > 0) {
     Card cc = p0_hand_.get(0);
     try {
       Minion mm = (Minion) cc;
       th += (cc.hasBeenUsed() ? 1 : 0) + mm.getAttack() + mm.getHealth() + cc.getMana();
     } catch (ClassCastException e) {
       th += (cc.hasBeenUsed() ? 1 : 0) + cc.getMana();
     }
   }
   if (hs > 1) {
     Card cc = p0_hand_.get(1);
     try {
       Minion mm = (Minion) cc;
       th += (cc.hasBeenUsed() ? 1 : 0) + mm.getAttack() + mm.getHealth() + cc.getMana();
     } catch (ClassCastException e) {
       th += (cc.hasBeenUsed() ? 1 : 0) + cc.getMana();
     }
   }
   res += (th % 10) * 1000000;
   int mh0 = 0;
   if (p0_minions_.size() > 0) {
     mh0 += p0_minions_.get(0).getHealth();
   }
   if (p0_minions_.size() > 1) {
     mh0 += p0_minions_.get(1).getHealth();
   }
   res += (mh0 % 100) * 10000000;
   int mh1 = 0;
   if (p1_minions_.size() > 0) {
     mh1 += p1_minions_.get(0).getHealth();
   }
   if (p1_minions_.size() > 1) {
     mh1 += p1_minions_.get(1).getHealth();
   }
   res += (mh1 % 20) * 100000000;
   return res;
 }
 /** Reset the has_been_used state of the cards in hand */
 public void resetHand() {
   for (Card card : p0_hand_) {
     card.hasBeenUsed(false);
   }
 }
  // TODO Unused?
  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;
  }