package com.hearthsim.util.factory; import com.hearthsim.card.Card; import com.hearthsim.card.CharacterIndex; import com.hearthsim.card.Deck; 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.HearthAction; import com.hearthsim.util.HearthAction.Verb; import com.hearthsim.util.tree.HearthTreeNode; import java.util.ArrayList; public class SparseChildNodeCreator extends ChildNodeCreatorBase { private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass()); public SparseChildNodeCreator(Deck deckPlayer0, Deck deckPlayer1) { super(deckPlayer0, deckPlayer1); } @Override public ArrayList<HearthTreeNode> createPlayCardChildren(HearthTreeNode boardStateNode) throws HSException { ArrayList<HearthTreeNode> nodes = new ArrayList<>(); Minion targetMinion; Card card; Card copiedCard; HearthTreeNode newState; PlayerModel currentPlayer = boardStateNode.data_.modelForSide(PlayerSide.CURRENT_PLAYER); PlayerModel waitingPlayer = boardStateNode.data_.modelForSide(PlayerSide.WAITING_PLAYER); boolean allUsed = true; int mana = boardStateNode.data_.getCurrentPlayer().getMana(); for (int cardIndex = 0; cardIndex < boardStateNode.data_.getCurrentPlayer().getHand().size(); ++cardIndex) { card = boardStateNode.data_.getCurrentPlayer().getHand().get(cardIndex); if (card == null) continue; // Should be impossible allUsed = allUsed && card.hasBeenUsed(); if (card.getManaCost(PlayerSide.CURRENT_PLAYER, boardStateNode.data_) > mana || card.hasBeenUsed()) { continue; } if (card instanceof Minion && !((Minion)card).getPlacementImportant()) { // If this card is a minion, then reduce the set of possible minion placement position CharacterIndex cardPlacementIndex = this.getMinionPlacementIndex(boardStateNode, (Minion)card); // actually place the card now targetMinion = boardStateNode.data_.getCurrentPlayer().getCharacter(cardPlacementIndex); if (card.canBeUsedOn(PlayerSide.CURRENT_PLAYER, targetMinion, boardStateNode.data_)) { newState = new HearthTreeNode(boardStateNode.data_.deepCopy()); copiedCard = newState.data_.getCurrentPlayer().getHand().get(cardIndex); newState = copiedCard.useOn(PlayerSide.CURRENT_PLAYER, cardPlacementIndex, newState); if (newState != null) { nodes.add(newState); } } } else { // we can use this card! Let's try using it on everything for (int tIndex = 0; tIndex <= currentPlayer.getNumMinions(); ++tIndex) { CharacterIndex targetIndex = CharacterIndex.fromInteger(tIndex); targetMinion = boardStateNode.data_.getCurrentPlayer().getCharacter(targetIndex); if (card.canBeUsedOn(PlayerSide.CURRENT_PLAYER, targetMinion, boardStateNode.data_)) { newState = new HearthTreeNode(boardStateNode.data_.deepCopy()); copiedCard = newState.data_.getCurrentPlayer().getHand().get(cardIndex); newState = copiedCard.useOn(PlayerSide.CURRENT_PLAYER, targetIndex, newState); if (newState != null) { nodes.add(newState); } } } for (int tIndex = 0; tIndex <= waitingPlayer.getNumMinions(); ++tIndex) { CharacterIndex targetIndex = CharacterIndex.fromInteger(tIndex); targetMinion = boardStateNode.data_.getWaitingPlayer().getCharacter(targetIndex); if (card.canBeUsedOn(PlayerSide.WAITING_PLAYER, targetMinion, boardStateNode.data_)) { newState = new HearthTreeNode(boardStateNode.data_.deepCopy()); copiedCard = newState.data_.getCurrentPlayer().getHand().get(cardIndex); newState = copiedCard.useOn(PlayerSide.WAITING_PLAYER, targetIndex, newState); if (newState != null) { nodes.add(newState); } } } } } // If no nodes were created then nothing could be played. If something could be played, we want to explicitly do nothing in its own node. if (!nodes.isEmpty()) { newState = new HearthTreeNode(boardStateNode.data_.deepCopy()); newState.setAction(new HearthAction(Verb.DO_NOT_USE_CARD)); for (Card c : newState.data_.getCurrentPlayer().getHand()) { c.hasBeenUsed(true); } nodes.add(newState); } return nodes; } protected CharacterIndex getMinionPlacementIndex(HearthTreeNode boardStateNode, Minion minion) { PlayerModel currentPlayer = boardStateNode.data_.modelForSide(PlayerSide.CURRENT_PLAYER); // If this card is a minion, then reduce the set of possible minion placement position int cardPlacementIndex = 0; // by default, place it to the left of everything // if there are minions on the board already, place the minion farthest away from the highest attack minion on the board if (currentPlayer.getNumMinions() > 1) { byte thisMinionAttack = minion.getTotalAttack(boardStateNode, PlayerSide.CURRENT_PLAYER); int numMinions = currentPlayer.getNumMinions(); byte maxAttack = -100; int maxAttackIndex = 0; byte secondMaxAttack = -100; int secondMaxAttackIndex = 0; for (int midx = 0; midx < numMinions; ++midx) { Minion tempMinion = currentPlayer.getCharacter(CharacterIndex.fromInteger(midx + 1)); if (tempMinion.getTotalAttack(boardStateNode, PlayerSide.CURRENT_PLAYER) >= maxAttack) { secondMaxAttackIndex = maxAttackIndex; secondMaxAttack = maxAttack; maxAttackIndex = midx; maxAttack = tempMinion.getTotalAttack(boardStateNode, PlayerSide.CURRENT_PLAYER); } else if (tempMinion.getTotalAttack(boardStateNode, PlayerSide.CURRENT_PLAYER) >= secondMaxAttack) { secondMaxAttackIndex = midx; secondMaxAttack = tempMinion.getTotalAttack(boardStateNode, PlayerSide.CURRENT_PLAYER); } } if (thisMinionAttack > secondMaxAttack && thisMinionAttack <= maxAttack) { // put this minion on the other side of maxAttack minion if (secondMaxAttackIndex < maxAttackIndex) cardPlacementIndex = 0; else cardPlacementIndex = numMinions; } else { // put this minion in between maxAttack and secondMaxAttack if (secondMaxAttackIndex < maxAttackIndex) { cardPlacementIndex = (maxAttackIndex + secondMaxAttackIndex + 1) / 2 - 1; } else { cardPlacementIndex = (maxAttackIndex + secondMaxAttackIndex) / 2; } } if (cardPlacementIndex < 0) { log.info("blah"); } } return CharacterIndex.fromInteger(cardPlacementIndex); } }