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.Hero;
import com.hearthsim.card.minion.Minion;
import com.hearthsim.util.DeepCopyable;
import com.hearthsim.util.IdentityLinkedList;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
public class PlayerModel implements DeepCopyable<PlayerModel>, Iterable<Minion> {
private final String name;
private final byte playerId; // used for identifying player 0 vs player 1
private final Hero hero;
private final Deck deck;
private byte mana;
private byte maxMana;
private byte deckPos;
private byte fatigueDamage;
// this uses identity list because we need exact reference equality and we modified Minion.equals
private IdentityLinkedList<Minion> minions;
private HandModel hand;
private byte overload;
private byte numCardsUsed;
public PlayerModel(byte playerId, String name, Hero hero, Deck deck) {
this.playerId = playerId;
this.name = name;
this.hero = hero;
this.deck = deck;
this.minions = new IdentityLinkedList<>();
this.hand = new HandModel();
this.numCardsUsed = 0;
this.deckPos = 0;
this.fatigueDamage = 1;
}
protected Card drawFromDeck(int index) {
Card card = deck.drawCard(index);
if (card == null) {
return null;
}
card.setInHand(true);
return card;
}
public Minion getCharacter(CharacterIndex index) {
if (index.getInt() > getNumMinions())
return null;
return index == CharacterIndex.HERO ? this.getHero() : this.minions.get(index.getInt() - 1);
}
public int getNumCharacters() {
return this.getNumMinions() + 1;
}
public int getNumMinions() {
if (minions == null)
return 0;
return minions.size();
}
public void addMinion(int position, Minion minion) {
this.minions.add(position, minion);
}
public boolean removeMinion(Minion minion) {
return this.minions.remove(minion);
}
public Deck getDeck() {
return deck;
}
public String getName() {
return name;
}
public Hero getHero() {
return hero;
}
public byte getMana() {
return mana;
}
public void setMana(byte mana) {
this.mana = mana;
}
public void addMana(byte value) {
this.mana += value;
}
public void subtractMana(byte value) {
this.mana -= value;
}
public byte getMaxMana() {
return maxMana;
}
public void setMaxMana(byte maxMana) {
this.maxMana = maxMana;
}
public void addMaxMana(byte value) {
this.maxMana += value;
}
public Iterable<Minion> getMinions() {
return minions;
}
public byte getSpellDamage() {
byte spellDamage = 0;
for (Minion minion : minions)
spellDamage += minion.getSpellDamage();
return spellDamage;
}
public IdentityLinkedList<Card> getHand() {
return hand;
}
public void setHand(HandModel hand) {
this.hand = hand;
}
public byte getOverload() {
return overload;
}
public void setOverload(byte overload) {
this.overload = overload;
}
public void addOverload(byte overload) {
this.overload += overload;
}
public byte getDeckPos() {
return deckPos;
}
public void setDeckPos(byte value) {
deckPos = value;
}
public void addDeckPos(byte value) {
deckPos += value;
}
public byte getFatigueDamage() {
return fatigueDamage;
}
public void setFatigueDamage(byte value) {
fatigueDamage = value;
}
public byte getNumCardsUsed() {
return numCardsUsed;
}
public void setNumCardsUsed(byte numCardsUsed) {
this.numCardsUsed = numCardsUsed;
}
public void addNumCardsUsed(byte value) {
this.numCardsUsed += value;
}
public boolean isComboEnabled() {
//don't count the card that was just used
return numCardsUsed > 1;
}
public boolean isBoardFull() {
return this.getNumMinions() >= 7;
}
public int getNumEmptyBoardSpace() {
return 7 - this.getNumMinions();
}
public boolean isHandFull() {
return this.hand.size() >= 10;
}
public CharacterIndex getIndexForCharacter(Minion character) {
if (this.hero.equals(character)) {
return CharacterIndex.HERO;
} else {
int minionIndex = this.minions.indexOf(character);
return minionIndex >= 0 ? CharacterIndex.fromInteger(this.minions.indexOf(character) + 1) : CharacterIndex.UNKNOWN;
}
}
@Override
public String toString() {
return new JSONObject(this).toString();
}
@Override
public PlayerModel deepCopy() {
PlayerModel copiedPlayerModel = new PlayerModel(
this.playerId,
this.name,
this.hero.deepCopy(),
this.deck //TODO should be a deep copy, we're just using the index in boardmodel right now to compensate..
//oyachai: the use of the deck position index is actually an attempt to reduce memory usage.
);
copiedPlayerModel.setMana(mana);
copiedPlayerModel.setMaxMana(maxMana);
copiedPlayerModel.setOverload(overload);
copiedPlayerModel.deckPos = deckPos;
copiedPlayerModel.fatigueDamage = fatigueDamage;
copiedPlayerModel.numCardsUsed = numCardsUsed;
for (Minion minion : minions) {
copiedPlayerModel.minions.add((Minion) (minion).deepCopy());
}
for (final Card card: hand) {
Card tc = card.deepCopy();
copiedPlayerModel.placeCardHand(tc);
}
return copiedPlayerModel;
}
public void placeCardHand(Card card) {
if (hand.isFull())
return;
card.setInHand(true);
hand.add(card);
}
public void placeCardHand(int cardIndex) {
Card card = drawFromDeck(cardIndex);
if (card != null)
placeCardHand(card);
}
public void placeCardDeck(Card card) {
deck.addCard(card);
}
public Card drawNextCardFromDeck() {
Card card = drawFromDeck(deckPos);
if (card == null) {
//no more card left in deck, take fatigue damage
hero.takeDamage(fatigueDamage);
++fatigueDamage;
} else {
placeCardHand(card);
++deckPos;
}
return card;
}
public ArrayList<Minion> getMinionsAdjacentToCharacter(CharacterIndex characterIndex) {
ArrayList<Minion> adjMinions = new ArrayList<>();
if (characterIndex.getInt() >= this.getNumCharacters()) {
return adjMinions;
}
if (characterIndex.getInt() > 1) {
CharacterIndex leftIndex = CharacterIndex.fromInteger(characterIndex.getInt() - 1);
adjMinions.add(this.getCharacter(leftIndex));
}
if (characterIndex.getInt() < (this.getNumCharacters() - 1)) {
CharacterIndex rightIndex = CharacterIndex.fromInteger(characterIndex.getInt() + 1);
adjMinions.add(this.getCharacter(rightIndex));
}
return adjMinions;
}
public byte getPlayerId() {
return playerId;
}
public void resetMana() {
mana = maxMana;
mana -= overload;
overload = 0;
resetNumCardsUsed();
}
protected void resetNumCardsUsed() {
numCardsUsed = 0;
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 31 + (null == name ? 0 : name.hashCode());
hash = hash * 31 + playerId;
hash = hash * 31 + (null == hero ? 0 : hero.hashCode());
hash = hash * 31 + (null == deck ? 0 : deck.hashCode());
hash = hash * 31 + mana;
hash = hash * 31 + maxMana;
hash = hash * 31 + deckPos;
hash = hash * 31 + fatigueDamage;
hash = hash * 31 + (null == minions ? 0 : minions.hashCode());
hash = hash * 31 + (null == hand ? 0 : hand.hashCode());
hash = hash * 31 + overload;
hash = hash * 31 + numCardsUsed;
return hash;
}
@Override
public boolean equals(Object other) {
if (other == null)
return false;
if (this.getClass() != other.getClass())
return false;
PlayerModel otherPlayer = (PlayerModel)other;
if (playerId != otherPlayer.playerId) return false;
if (mana != otherPlayer.mana) return false;
if (maxMana != otherPlayer.maxMana) return false;
if (overload != otherPlayer.overload) return false;
if (deckPos != otherPlayer.deckPos) return false;
if (fatigueDamage != otherPlayer.fatigueDamage) return false;
if (!name.equals(otherPlayer.name)) return false;
if (!hero.equals(otherPlayer.hero)) return false;
if (deck != null && !deck.equals(otherPlayer.deck)) return false;
if (!minions.equals(otherPlayer.minions)) return false;
if (!hand.equals(otherPlayer.hand)) return false;
if (numCardsUsed != otherPlayer.numCardsUsed) return false;
return true;
}
public JSONObject toJSON() {
JSONObject json = new JSONObject();
json.put("name", name);
json.put("playerId", playerId);
json.put("hero", hero.toJSON());
if (mana != maxMana) json.put("mana", mana);
if (maxMana > 0) json.put("maxMana", maxMana);
json.put("deckPos", deckPos);
if (overload > 0) json.put("overload", overload);
if (fatigueDamage > 0) json.put("fatigueDamage", fatigueDamage);
if (minions.size() > 0) {
JSONArray array = new JSONArray();
for (Minion minion: minions) {
array.put(minion.toJSON());
}
json.put("minions", array);
}
if (hand.size() > 0) {
JSONArray array = new JSONArray();
for (Card card: hand) {
array.put(card.toJSON());
}
json.put("hand", array);
}
if (numCardsUsed > 0) {
json.put("numCardsUsed", numCardsUsed);
}
return json;
}
@Override
public CharacterIterator iterator() {
return this.characterIterator();
}
public CharacterIterator characterIterator() {
return new CharacterIterator(this);
}
public HandIterator handIterator() {
return new HandIterator(this);
}
public class CharacterIterator implements Iterator<Minion> {
private int location = -1;
private final PlayerModel target;
public CharacterIterator(PlayerModel model) {
this.target = model;
}
// used by the BoardModel master iterator
public CharacterIndex getLocation() {
return CharacterIndex.fromInteger(location);
}
@Override
public boolean hasNext() {
return location < (this.target.getNumCharacters() - 1);
}
@Override
public Minion next() {
return this.target.getCharacter(CharacterIndex.fromInteger(++location));
}
}
public class HandIterator implements Iterator<Card> {
private int location = -1;
private final PlayerModel target;
public HandIterator(PlayerModel model) {
this.target = model;
}
// used by the BoardModel master iterator
public CardInHandIndex getLocation() {
return CardInHandIndex.fromInteger(location);
}
@Override
public boolean hasNext() {
return location < (this.target.getHand().size() - 1);
}
@Override
public Card next() {
return this.target.getHand().get(++location);
}
}
}