package com.hearthsim.model;
import com.hearthsim.card.Card;
import com.hearthsim.card.CardInHandIndex;
import com.hearthsim.card.CharacterIndex;
import com.hearthsim.card.Deck;
import com.hearthsim.card.minion.AuraTargetType;
import com.hearthsim.card.minion.Hero;
import com.hearthsim.card.minion.Minion;
import com.hearthsim.card.minion.MinionWithAura;
import com.hearthsim.card.minion.heroes.TestHero;
import com.hearthsim.event.effect.ActiveEffectHand;
import com.hearthsim.event.effect.SimpleEffectHand;
import com.hearthsim.event.filter.FilterHand;
import com.hearthsim.util.DeepCopyable;
import com.hearthsim.util.IdentityLinkedList;
import org.json.JSONObject;
import org.slf4j.MDC;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
/**
* A class that represents the current state of the board (game)
*
*/
public class BoardModel implements DeepCopyable<BoardModel>, Iterable<CharacterIndex.CharacterLocation> {
private final PlayerModel currentPlayer;
private final PlayerModel waitingPlayer;
// this uses identity list because we need exact reference equality and we modified Minion.equals
private IdentityLinkedList<MinionPlayerPair> allMinionsFIFOList_;
public class MinionPlayerPair {
private final Minion minion;
private PlayerSide playerSide;
public MinionPlayerPair(Minion minion, PlayerSide playerSide) {
this.minion = minion;
this.playerSide = playerSide;
}
public Minion getMinion() {
return minion;
}
public PlayerSide getPlayerSide() {
return playerSide;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MinionPlayerPair that = (MinionPlayerPair) o;
if (minion != null ? !minion.equals(that.minion) : that.minion != null) return false;
if (playerSide != null ? !playerSide.equals(that.playerSide) : that.playerSide != null) return false;
return true;
}
@Override
public int hashCode() {
int result = minion != null ? minion.hashCode() : 0;
result = 31 * result + (playerSide != null ? playerSide.hashCode() : 0);
return result;
}
}
private class CharacterLocationIterator implements Iterator<CharacterIndex.CharacterLocation> {
private PlayerSide playerSide;
private PlayerModel.CharacterIterator characterIterator;
private final BoardModel model;
public CharacterLocationIterator(BoardModel model) {
this.model = model;
this.playerSide = PlayerSide.CURRENT_PLAYER;
this.characterIterator = this.model.modelForSide(this.playerSide).iterator();
}
@Override
public boolean hasNext() {
// there is always at least one on the waiting player side
if (this.playerSide == PlayerSide.CURRENT_PLAYER) {
return true;
}
return this.characterIterator.hasNext();
}
@Override
public CharacterIndex.CharacterLocation next() {
if (this.characterIterator.hasNext()) {
this.characterIterator.next();
} else if (this.playerSide == PlayerSide.CURRENT_PLAYER) {
this.playerSide = this.playerSide.getOtherPlayer();
this.characterIterator = this.model.modelForSide(this.playerSide).iterator();
this.characterIterator.next();
}
return new CharacterIndex.CharacterLocation(this.playerSide, this.characterIterator.getLocation());
}
}
private class HandLocationIterator implements Iterator<CardInHandIndex.CardInHandLocation> {
private PlayerSide playerSide;
private PlayerModel.HandIterator handIterator;
private final BoardModel model;
public HandLocationIterator(BoardModel model) {
this.model = model;
this.playerSide = PlayerSide.CURRENT_PLAYER;
this.handIterator = this.model.modelForSide(this.playerSide).handIterator();
}
@Override
public boolean hasNext() {
if (this.handIterator.hasNext()) {
return true;
}
// check hand size for other player
if (this.playerSide == PlayerSide.CURRENT_PLAYER) {
return this.model.modelForSide(this.playerSide.getOtherPlayer()).getHand().size() > 0;
}
return false;
}
@Override
public CardInHandIndex.CardInHandLocation next() {
if (this.handIterator.hasNext()) {
this.handIterator.next();
} else if (this.playerSide == PlayerSide.CURRENT_PLAYER) {
this.playerSide = this.playerSide.getOtherPlayer();
this.handIterator = this.model.modelForSide(this.playerSide).handIterator();
this.handIterator.next();
}
return new CardInHandIndex.CardInHandLocation(this.playerSide, this.handIterator.getLocation());
}
}
@Override
public Iterator<CharacterIndex.CharacterLocation> iterator() {
return this.characterIterator();
}
public Iterator<CharacterIndex.CharacterLocation> characterIterator() {
return new CharacterLocationIterator(this);
}
public Iterator<CardInHandIndex.CardInHandLocation> handIterator() {
return new HandLocationIterator(this);
}
public BoardModel() {
this(new PlayerModel((byte)0, "player0", new TestHero("hero0", (byte)30), new Deck()),
new PlayerModel((byte)1, "player1", new TestHero("hero1", (byte)30), new Deck()));
}
public BoardModel(Deck deckPlayer0, Deck deckPlayer1) {
this(new PlayerModel((byte)0, "player0", new TestHero("hero0", (byte)30), deckPlayer0),
new PlayerModel((byte)1, "player1", new TestHero("hero1", (byte)30), deckPlayer1));
}
public BoardModel(Hero p0_hero, Hero p1_hero) {
this(new PlayerModel((byte)0, "p0",p0_hero,new Deck()), new PlayerModel((byte)1, "p1",p1_hero,new Deck()));
}
public BoardModel(PlayerModel currentPlayerModel, PlayerModel waitingPlayerModel) {
this.currentPlayer = currentPlayerModel;
this.waitingPlayer = waitingPlayerModel;
this.allMinionsFIFOList_ = new IdentityLinkedList<>();
}
public PlayerModel modelForSide(PlayerSide side){
PlayerModel model;
switch (side) {
case CURRENT_PLAYER: model = currentPlayer;
break;
case WAITING_PLAYER: model = waitingPlayer;
break;
default:
throw new RuntimeException("unexpected player side");
}
return model;
}
/**
* Count all minions in play regardless of player side
* @return The number of all minions in play
*/
public int getTotalMinionCount() {
return this.allMinionsFIFOList_.size();
}
public Card getCard_hand(PlayerSide playerSide, CardInHandIndex index) {
return modelForSide(playerSide).getHand().get(index.getInt());
}
public Minion getCharacter(CharacterIndex.CharacterLocation location) {
return this.getCharacter(location.getPlayerSide(), location.getIndex());
}
public Minion getCharacter(PlayerSide playerSide, CharacterIndex index) {
PlayerModel playerModel = modelForSide(playerSide);
return playerModel.getCharacter(index);
}
//-----------------------------------------------------------------------------------
// Various ways to put a minion onto board
//-----------------------------------------------------------------------------------
/**
* Place a minion onto the board. Does not trigger any events, but applies all auras.
*
* This is a function to place a minion on the board. Use this function only if you
* want no events to be trigger upon placing the minion.
*
*
* @param playerSide
* @param minion The minion to be placed on the board.
* @param position The position to place the minion. The new minion goes to the "left" (lower index) of the postinion index.
*/
public void placeMinion(PlayerSide playerSide, Minion minion, CharacterIndex position) {
PlayerModel playerModel = modelForSide(playerSide);
playerModel.addMinion(position.getInt(), minion);
this.allMinionsFIFOList_.add(new MinionPlayerPair(minion, playerSide));
minion.setInHand(false);
minion.setManaDelta((byte) 0); // once the minion hits the board it resets its mana cost
//Apply the aura if any
applyAuraOfMinion(playerSide, minion);
applyOtherMinionsAura(playerSide, minion);
}
/**
* Place a minion onto the board. Does not trigger any events, but applies all auras.
*
* This is a function to place a minion on the board. Use this function only if you
* want no events to be trigger upon placing the minion.
*
*
* @param playerSide
* @param minion The minion to be placed on the board. The minion is placed on the right-most space.
*/
public void placeMinion(PlayerSide playerSide, Minion minion) {
this.placeMinion(playerSide, minion, CharacterIndex.fromInteger(this.modelForSide(playerSide).getNumMinions()));
}
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
public void placeCardHand(PlayerSide side, Card card){
modelForSide(side).placeCardHand(card);
}
public void removeCardFromHand(Card card, PlayerSide playerSide){
modelForSide(playerSide).getHand().remove(card);
}
public int getNumCards_hand(PlayerSide playerSide) {
return modelForSide(playerSide).getHand().size();
}
public void placeCardDeck(PlayerSide side, Card card){
modelForSide(side).placeCardDeck(card);
}
/**
* Draw a card from a deck and place it in the hand
*
* @param numCards Number of cards to draw.
*/
public void drawCardFromWaitingPlayerDeck(int numCards) {
//This minion is an enemy minion. Let's draw a card for the enemy. No need to use a StopNode for enemy card draws.
for (int indx = 0; indx < numCards; ++indx) {
this.getWaitingPlayer().drawNextCardFromDeck();
}
}
/**
* Draw a card from a deck and place it in the hand without using a CardDrawNode
*
* Note: It is almost always correct to use CardDrawNode instead of this function!!!!
*
* @param numCards Number of cards to draw.
*/
public void drawCardFromCurrentPlayerDeck(int numCards) {
for (int indx = 0; indx < numCards; ++indx) {
this.getCurrentPlayer().drawNextCardFromDeck();
}
}
public boolean removeMinion(MinionPlayerPair minionIdPair) {
this.allMinionsFIFOList_.remove(minionIdPair);
removeAuraOfMinion(minionIdPair.getPlayerSide(), minionIdPair.minion);
return this.modelForSide(minionIdPair.getPlayerSide()).removeMinion(minionIdPair.minion);
}
public boolean removeMinion(Minion minion) {
MinionPlayerPair mP = null;
for (MinionPlayerPair minionIdPair : this.allMinionsFIFOList_) {
if (minionIdPair.minion == minion) {
mP = minionIdPair;
break;
}
}
return this.removeMinion(mP);
}
public boolean removeMinion(PlayerSide side, CharacterIndex minionIndex) {
return removeMinion(this.getCharacter(side, minionIndex));
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
// Aura Handling
//
// Aura handling is delegated to BoardModel rather than Minion class
//----------------------------------------------------------------------------
/**
* Applies any aura that the given minion has
*
* @param side The PlayerSide of the minion
* @param minion
*/
public void applyAuraOfMinion(PlayerSide side, Minion minion) {
if (minion instanceof MinionWithAura && !minion.isSilenced()) {
EnumSet<AuraTargetType> targetTypes = ((MinionWithAura)minion).getAuraTargets();
for (AuraTargetType targetType : targetTypes) {
switch (targetType) {
case AURA_FRIENDLY_MINIONS:
for (Minion targetMinion : this.modelForSide(side).getMinions()) {
if (targetMinion != minion)
((MinionWithAura) minion).applyAura(side, targetMinion, this);
}
break;
case AURA_ENEMY_MINIONS:
for (Minion targetMinion : this.modelForSide(side.getOtherPlayer()).getMinions()) {
((MinionWithAura) minion).applyAura(side, targetMinion, this);
}
break;
default:
break;
}
}
}
if (minion instanceof ActiveEffectHand) {
ActiveEffectHand activeEffect = (ActiveEffectHand)minion;
if (activeEffect.isActive(side, minion, this)) {
FilterHand filter = activeEffect.getActiveFilter();
SimpleEffectHand effect = activeEffect.getActiveEffect();
Iterator<CardInHandIndex.CardInHandLocation> handIterator = this.handIterator();
while (handIterator.hasNext()) {
CardInHandIndex.CardInHandLocation location = handIterator.next();
if (filter.targetMatches(side, minion, location.getPlayerSide(), location.getIndex(), this)) {
effect.applyEffect(side, minion, location.getPlayerSide(), location.getIndex(), this);
}
}
}
}
}
/**
* Applies any aura effects that other minions on the board might have
*
* @param side The PlayerSide of the minion to apply the auras to
* @param minion
*/
private void applyOtherMinionsAura(PlayerSide side, Minion minion) {
for (Minion otherMinion : this.modelForSide(side).getMinions()) {
if (otherMinion instanceof MinionWithAura &&
minion != otherMinion &&
!otherMinion.isSilenced() &&
((MinionWithAura)otherMinion).getAuraTargets().contains(AuraTargetType.AURA_FRIENDLY_MINIONS)) {
((MinionWithAura)otherMinion).applyAura(side, minion, this);
}
}
for (Minion otherMinion : this.modelForSide(side.getOtherPlayer()).getMinions()) {
if (otherMinion instanceof MinionWithAura &&
minion != otherMinion &&
!otherMinion.isSilenced() &&
((MinionWithAura)otherMinion).getAuraTargets().contains(AuraTargetType.AURA_ENEMY_MINIONS)) {
((MinionWithAura)otherMinion).applyAura(side, minion, this);
}
}
}
public void applyAurasToCardInHand(PlayerSide targetSide, Card target) {
if (target == null)
return;
Iterator<CharacterIndex.CharacterLocation> characterIterator = this.characterIterator();
while (characterIterator.hasNext()) {
CharacterIndex.CharacterLocation location = characterIterator.next();
Minion character = this.getCharacter(location);
if (character instanceof ActiveEffectHand) {
ActiveEffectHand activeEffect = (ActiveEffectHand)character;
if (activeEffect.isActive(location.getPlayerSide(), character, this)) {
FilterHand filter = activeEffect.getActiveFilter();
SimpleEffectHand effect = activeEffect.getActiveEffect();
if (filter.targetMatches(location.getPlayerSide(), character, targetSide, target, this)) {
effect.applyEffect(location.getPlayerSide(), character, targetSide, target, this);
}
}
}
}
}
/**
* Removes any aura that the given minion might have
*
* @param side The PlayerSide of the minion
* @param minion
*/
public void removeAuraOfMinion(PlayerSide side, Minion minion) {
if (minion instanceof MinionWithAura && !minion.isSilenced()) {
EnumSet<AuraTargetType> targetTypes = ((MinionWithAura)minion).getAuraTargets();
for (AuraTargetType targetType : targetTypes) {
switch (targetType) {
case AURA_FRIENDLY_MINIONS:
for (Minion targetMinion : this.modelForSide(side).getMinions()) {
if (targetMinion != minion)
((MinionWithAura) minion).removeAura(side, targetMinion, this);
}
break;
case AURA_ENEMY_MINIONS:
for (Minion targetMinion : this.modelForSide(side.getOtherPlayer()).getMinions()) {
((MinionWithAura) minion).removeAura(side, targetMinion, this);
}
break;
default:
break;
}
}
}
if (minion instanceof ActiveEffectHand) {
ActiveEffectHand activeEffect = (ActiveEffectHand)minion;
if (activeEffect.isActive(side, minion, this)) {
FilterHand filter = activeEffect.getActiveFilter();
SimpleEffectHand effect = activeEffect.undoActiveEffect();
Iterator<CardInHandIndex.CardInHandLocation> handIterator = this.handIterator();
while (handIterator.hasNext()) {
CardInHandIndex.CardInHandLocation location = handIterator.next();
if (filter.targetMatches(side, minion, location.getPlayerSide(), location.getIndex(), this)) {
effect.applyEffect(side, minion, location.getPlayerSide(), location.getIndex(), this);
}
}
}
}
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
public boolean isDead(PlayerSide playerSide) {
return modelForSide(playerSide).getHero().getHealth() <= 0;
}
public boolean isLethalState() {
return this.isDead(PlayerSide.CURRENT_PLAYER) || this.isDead(PlayerSide.WAITING_PLAYER);
}
@SuppressWarnings("unused")
public ArrayList<Integer> getAttackableMinions() {
ArrayList<Integer> toRet = new ArrayList<>();
boolean hasTaunt = false;
for (final Minion minion : waitingPlayer.getMinions()) {
hasTaunt = hasTaunt || minion.getTaunt();
}
if (!hasTaunt) {
toRet.add(0);
int counter = 1;
for (Minion ignored : waitingPlayer.getMinions()) {
toRet.add(counter);
counter++;
}
return toRet;
} else {
int counter = 1;
for (Minion aP1_minions_ : waitingPlayer.getMinions()) {
if (aP1_minions_.getTaunt())
toRet.add(counter);
counter++;
}
return toRet;
}
}
//Dead minion check
/**
* Checks to see if there are dead minions
* @return
*/
public boolean hasDeadMinions() {
for (MinionPlayerPair pair : this.allMinionsFIFOList_) {
if (pair.minion.getTotalHealth() <= 0) {
return true;
}
}
return false;
}
// public Collection<Minion> getAllMinions() {
// ArrayList<Minion> minions = new ArrayList<>();
// minions.addAll(this.modelForSide(PlayerSide.CURRENT_PLAYER).getMinions());
// minions.addAll(this.modelForSide(PlayerSide.WAITING_PLAYER).getMinions());
// return minions;
// }
public Iterable<MinionPlayerPair> getAllMinionsFIFOList() {
return allMinionsFIFOList_;
}
public void resetMana() {
currentPlayer.resetMana();
waitingPlayer.resetMana();
}
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (this.getClass() != other.getClass()) {
return false;
}
BoardModel bOther = (BoardModel)other;
if (!currentPlayer.equals(bOther.currentPlayer)) return false;
if (!waitingPlayer.equals(bOther.waitingPlayer)) return false;
if (!allMinionsFIFOList_.equals(bOther.allMinionsFIFOList_)) return false;
return true;
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 31 + currentPlayer.hashCode();
hash = hash * 31 + waitingPlayer.hashCode();
hash = hash * 31 + allMinionsFIFOList_.hashCode();
return hash;
}
/**
* Reset all minions to clear their has_attacked state.
*
* Call this function at the beginning of each turn
*
*/
public void resetMinions() {
currentPlayer.getHero().hasAttacked(false);
currentPlayer.getHero().hasBeenUsed(false);
waitingPlayer.getHero().hasAttacked(false);
waitingPlayer.getHero().hasBeenUsed(false);
for (Minion minion : this.modelForSide(PlayerSide.CURRENT_PLAYER).getMinions()) {
minion.hasAttacked(false);
minion.hasBeenUsed(false);
}
for (Minion minion : this.modelForSide(PlayerSide.WAITING_PLAYER).getMinions()) {
minion.hasAttacked(false);
minion.hasBeenUsed(false);
}
}
/**
* Reset the has_been_used state of the cards in hand
*/
public void resetHand() {
for (Card card : currentPlayer.getHand()) {
card.hasBeenUsed(false);
}
}
public BoardModel flipPlayers() {
BoardModel newBoard = new BoardModel(waitingPlayer.deepCopy(), currentPlayer.deepCopy());
for (MinionPlayerPair minionPlayerPair : allMinionsFIFOList_) {
PlayerModel oldPlayerModel = this.modelForSide(minionPlayerPair.getPlayerSide());
Minion oldMinion = minionPlayerPair.getMinion();
CharacterIndex indexOfOldMinion = oldPlayerModel.getIndexForCharacter(oldMinion);
PlayerModel newPlayerModel = newBoard.modelForSide(minionPlayerPair.getPlayerSide().getOtherPlayer());
newBoard.allMinionsFIFOList_.add(new MinionPlayerPair(newPlayerModel.getCharacter(indexOfOldMinion), minionPlayerPair.getPlayerSide().getOtherPlayer()));
}
return newBoard;
}
@Override
public BoardModel deepCopy() {
BoardModel newBoard = new BoardModel(currentPlayer.deepCopy(), waitingPlayer.deepCopy());
for (MinionPlayerPair minionPlayerPair : allMinionsFIFOList_) {
PlayerModel oldPlayerModel = this.modelForSide(minionPlayerPair.getPlayerSide());
Minion oldMinion = minionPlayerPair.getMinion();
CharacterIndex indexOfOldMinion = oldPlayerModel.getIndexForCharacter(oldMinion);
PlayerModel newPlayerModel = newBoard.modelForSide(minionPlayerPair.getPlayerSide());
newBoard.allMinionsFIFOList_.add(new MinionPlayerPair(newPlayerModel.getCharacter(indexOfOldMinion), minionPlayerPair.getPlayerSide()));
}
return newBoard;
}
public JSONObject toJSON() {
JSONObject json = new JSONObject();
json.put("currentPlayer", currentPlayer.toJSON());
json.put("waitingPlayer", waitingPlayer.toJSON());
return json;
}
@Override
public String toString() {
String boardFormat = MDC.get("board_format");
if (boardFormat != null && boardFormat.equals("simple")) {
return simpleString();
} else {
return jsonString();
}
}
private String simpleString() {
StringBuilder stringBuffer = new StringBuilder();
stringBuffer.append("[");
stringBuffer.append("P0_health:").append(currentPlayer.getHero().getHealth()).append(", ");
stringBuffer.append("P0_mana:").append(currentPlayer.getMana()).append(", ");
stringBuffer.append("P1_health:").append(waitingPlayer.getHero().getHealth()).append(", ");
stringBuffer.append("P1_mana:").append(waitingPlayer.getMana()).append(", ");
stringBuffer.append("]");
return stringBuffer.toString();
}
private String jsonString() {
return this.toJSON().toString();
}
public PlayerModel getCurrentPlayer() {
return currentPlayer;
}
public PlayerModel getWaitingPlayer() {
return waitingPlayer;
}
@Deprecated
public Card getCurrentPlayerCardHand(int index) {
return currentPlayer.getHand().get(index);
}
}