public class GameState {
	
	public static int TURN_LIMIT = 100;
	public static boolean RANDOMNESS = false;
	public static int STARTING_AP = 3;
	public static int ACTION_POINTS = 5;
	
	private static final int ASSAULT_BONUS = 300;
	private static final double INFERNO_DAMAGE = 350;
	private static final int REQUIRED_UNITS = 3;
	private static final int POTION_REVIVE = 100;
	private static final int POTION_HEAL = 1000;
	public static final boolean OPEN_HANDS = false;

	public HaMap map;
	public boolean p1Turn;
	public int turn;
	public int APLeft;
	public Unit[][] units;
	public CardSet p1Deck;
	public CardSet p2Deck;
	public CardSet p1Hand;
	public CardSet p2Hand;
	public boolean isTerminal;

	public List<Position> chainTargets;

	public GameState(HaMap map) {
		super();
		isTerminal = false;
		this.map = map;
		p1Turn = true;
		turn = 1;
		APLeft = STARTING_AP;
		p1Hand = new CardSet();
		p2Hand = new CardSet();
		p1Deck = new CardSet();
		p2Deck = new CardSet();
		chainTargets = new ArrayList<Position>();
		if (map != null)
			units = new Unit[map.width][map.height];
		else
			units = new Unit[0][0];
	}

	public GameState(HaMap map, boolean p1Turn, int turn, int APLeft,
			Unit[][] units, CardSet p1Hand, CardSet p2Hand, CardSet p1Deck,
			CardSet p2Deck, List<Position> chainTargets, boolean isTerminal) {
		super();
		this.map = map;
		this.p1Turn = p1Turn;
		this.turn = turn;
		this.APLeft = APLeft;
		this.units = units;
		this.p1Hand = p1Hand;
		this.p2Hand = p2Hand;
		this.p1Deck = p1Deck;
		this.p2Deck = p2Deck;
		this.chainTargets = chainTargets;
		this.isTerminal = isTerminal;
		
	}

	public void init(DECK_SIZE deckSize) {
		p1Hand = new CardSet();
		p2Hand = new CardSet();
		p1Deck = new CardSet();
		p2Deck = new CardSet();
		shuffleDecks(deckSize);
		dealCards();
		for (final Position pos : map.p1Crystals) {
			units[pos.x][pos.y] = new Unit(Card.CRYSTAL, true);
			units[pos.x][pos.y].init(Card.CRYSTAL, true);
		}
		for (final Position pos : map.p2Crystals) {
			units[pos.x][pos.y] = new Unit(Card.CRYSTAL, false);
			units[pos.x][pos.y].init(Card.CRYSTAL, false);
		}
	}

	private void shuffleDecks(DECK_SIZE deckSize) {
		for (final Card type : Council.deck(deckSize)) {
			p1Deck.add(type);
			p2Deck.add(type);
		}
	}

	public void possibleActions(List<Action> actions) {

		actions.clear();

		if (APLeft == 0) {
			actions.add(SingletonAction.endTurnAction);
			return;
		}

		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null)
					possibleActions(units[x][y], SingletonAction.positions[x][y], actions);

		final List<Card> visited = new ArrayList<Card>();
		for (final Card card : Card.values())
			if (currentHand().contains(card)) {
				possibleActions(card, actions);
				visited.add(card);
			}

	}

	public void possibleActions(Card card, List<Action> actions) {

		if (APLeft == 0)
			return;

		if (card.type == CardType.ITEM) {
			for (int x = 0; x < map.width; x++)
				for (int y = 0; y < map.height; y++)
					if (units[x][y] != null
							&& units[x][y].unitClass.card != Card.CRYSTAL) {
						if (units[x][y].equipment.contains(card))
							continue;
						if (units[x][y].p1Owner == p1Turn) {
							if (card == Card.REVIVE_POTION
									&& units[x][y].fullHealth())
								continue;
							if (card != Card.REVIVE_POTION
									&& units[x][y].hp == 0)
								continue;
							actions.add(new DropAction(card, SingletonAction.positions[x][y]));
						}
					}
		} else if (card.type == CardType.SPELL)
			for (int x = 0; x < map.width; x++)
				for (int y = 0; y < map.height; y++)
					actions.add(new DropAction(card, SingletonAction.positions[x][y]));
		else if (card.type == CardType.UNIT)
			if (p1Turn) {
				for (final Position pos : map.p1DeploySquares)
					if (units[pos.x][pos.y] == null
							|| units[pos.x][pos.y].hp == 0)
						actions.add(new DropAction(card, SingletonAction.positions[pos.x][pos.y]));
			} else
				for (final Position pos : map.p2DeploySquares)
					if (units[pos.x][pos.y] == null
							|| units[pos.x][pos.y].hp == 0)
						actions.add(new DropAction(card, SingletonAction.positions[pos.x][pos.y]));

		if (!currentDeck().isEmpty())
			actions.add(SingletonAction.swapActions.get(card));

	}

	public void possibleActions(Unit unit, Position from, List<Action> actions) {

		if (unit.unitClass.card == Card.CRYSTAL)
			return;

		if (APLeft == 0 || unit.hp == 0 || APLeft == 0
				|| unit.p1Owner != p1Turn)
			return;

		// Movement and attack
		int d = unit.unitClass.speed;
		if (unit.unitClass.heal != null && unit.unitClass.heal.range > d)
			d = unit.unitClass.heal.range;
		if (unit.unitClass.attack != null && unit.unitClass.attack.range > d)
			d = unit.unitClass.attack.range;
		if (unit.unitClass.swap)
			d = Math.max(map.width, map.height);
		for (int x = d * (-1); x <= d; x++)
			for (int y = d * (-1); y <= d; y++) {
				if (from.x + x >= map.width || from.x + x < 0 || from.y + y >= map.height || from.y + y < 0)
					continue;
				
				final Position to = SingletonAction.positions[from.x + x][from.y +y];
				
				if (to.equals(from))
					continue;

				if (units[to.x][to.y] != null) {

					if (units[to.x][to.y].hp == 0) {

						if ((map.squares[to.x][to.y] == SquareType.DEPLOY_1 && !p1Turn)
								|| (map.squares[to.x][to.y] == SquareType.DEPLOY_2 && p1Turn)) {
							// NOT ALLOWED!
						} else if (unit.unitClass.heal != null
								&& from.distance(to) <= unit.unitClass.heal.range)
							actions.add(new UnitAction(from, to,
									UnitActionType.HEAL));
						else if (from.distance(to) <= unit.unitClass.speed)
							actions.add(new UnitAction(from, to,
									UnitActionType.MOVE));
					} else {
						final int distance = from.distance(to);
						if (unit.p1Owner != units[to.x][to.y].p1Owner
								&& distance <= unit.unitClass.attack.range) {
							if (!(distance > 1 && losBlocked(p1Turn, from, to)))
								actions.add(new UnitAction(from, to,
										UnitActionType.ATTACK));
						} else if (unit.p1Owner == units[to.x][to.y].p1Owner
								&& unit.unitClass.heal != null
								&& from.distance(to) <= unit.unitClass.heal.range
								&& !units[to.x][to.y].fullHealth()
								&& units[to.x][to.y].unitClass.card != Card.CRYSTAL)
							actions.add(new UnitAction(from, to,
									UnitActionType.HEAL));
						else if (unit.p1Owner == units[to.x][to.y].p1Owner
								&& unit.unitClass.swap
								&& units[to.x][to.y].unitClass.card != Card.CRYSTAL)
							actions.add(new UnitAction(from, to,
									UnitActionType.SWAP));
					}

				} else if (from.distance(to) <= unit.unitClass.speed)
					if ((map.squares[to.x][to.y] == SquareType.DEPLOY_1 && !p1Turn)
							|| (map.squares[to.x][to.y] == SquareType.DEPLOY_2 && p1Turn)) {
						// NOT ALLOWED!
					} else
						actions.add(new UnitAction(from, to,
								UnitActionType.MOVE));
			}
	}

	public void update(List<Action> actions) {
		for (final Action action : actions)
			update(action);
	}

	public void update(Action action) {

		try {
			chainTargets.clear();

			if (action instanceof EndTurnAction || APLeft <= 0)
				endTurn();

			if (action instanceof DropAction) {

				final DropAction drop = (DropAction) action;

				// Not a type in current players hand
				if (!currentHand().contains(drop.type))
					return;

				// Unit
				if (drop.type.type == CardType.UNIT) {

					// Not current players deploy square
					if (map.squares[drop.to.x][drop.to.y] == SquareType.DEPLOY_1
							&& !p1Turn)
						return;
					if (map.squares[drop.to.x][drop.to.y] == SquareType.DEPLOY_2
							&& p1Turn)
						return;

					deploy(drop.type, drop.to);

				}

				// Equipment
				if (drop.type.type == CardType.ITEM) {

					// Not a unit square or crystal
					if (units[drop.to.x][drop.to.y] == null
							|| units[drop.to.x][drop.to.y].unitClass.card == Card.CRYSTAL)
						return;

					if (units[drop.to.x][drop.to.y].p1Owner != p1Turn)
						return;

					if (drop.type == Card.REVIVE_POTION
							&& (units[drop.to.x][drop.to.y].unitClass.card == Card.CRYSTAL || units[drop.to.x][drop.to.y]
									.fullHealth()))
						return;

					if (drop.type != Card.REVIVE_POTION
							&& units[drop.to.x][drop.to.y].hp == 0)
						return;

					if (units[drop.to.x][drop.to.y].equipment
							.contains(drop.type))
						return;

					equip(drop.type, drop.to);

				}

				// Spell
				if (drop.type.type == CardType.SPELL)
					dropInferno(drop.to);

				return;

			}

			if (action instanceof UnitAction) {

				final UnitAction ua = (UnitAction) action;

				if (units[ua.from.x][ua.from.y] == null)
					return;

				final Unit unit = units[ua.from.x][ua.from.y];

				if (unit == null)
					return;
				
				if (unit.p1Owner != p1Turn)
					return;

				if (unit.hp == 0)
					return;

				// Move
				if (units[ua.to.x][ua.to.y] == null
						|| (unit.p1Owner == units[ua.to.x][ua.to.y].p1Owner
								&& units[ua.to.x][ua.to.y].hp == 0 && unit.unitClass.heal == null)
						|| (unit.p1Owner != units[ua.to.x][ua.to.y].p1Owner && units[ua.to.x][ua.to.y].hp == 0)) {

					if (ua.from.distance(ua.to) > units[ua.from.x][ua.from.y].unitClass.speed)
						return;

					if (map.squares[ua.to.x][ua.to.y] == SquareType.DEPLOY_1
							&& !unit.p1Owner)
						return;

					if (map.squares[ua.to.x][ua.to.y] == SquareType.DEPLOY_2
							&& unit.p1Owner)
						return;

					move(unit, ua.from, ua.to);
					return;

				} else {

					final Unit other = units[ua.to.x][ua.to.y];

					// Swap and heal
					if (unit.p1Owner == other.p1Owner) {
						if (unit.unitClass.swap
								&& units[ua.to.x][ua.to.y].unitClass.card != Card.CRYSTAL
								&& units[ua.to.x][ua.to.y].hp != 0) {
							swap(unit, ua.from, other, ua.to);
							return;
						}
						if (unit.unitClass.heal == null)
							return;
						if (ua.from.distance(ua.to) > unit.unitClass.heal.range)
							return;
						if (other.fullHealth())
							return;
						heal(unit, ua.from, other);
						return;
					}

					// Attack
					final int distance = ua.from.distance(ua.to);
					if (unit.unitClass.attack != null
							&& distance > unit.unitClass.attack.range)
						return;

					if (distance > 1 && losBlocked(p1Turn, ua.from, ua.to))
						return;

					if (other == null)
						return;
					
					attack(unit, ua.from, other, ua.to);
					return;

				}
			}

			if (action instanceof SwapCardAction) {

				final Card card = ((SwapCardAction) action).card;

				if (currentHand().contains(card)) {

					currentDeck().add(card);
					currentHand().remove(card);
					APLeft--;

				}

			}
		} catch (final Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * Using the Bresenham-based super-cover line algorithm
	 * 
	 * @param from
	 * @param to
	 * @return
	 */
	public boolean losBlocked(boolean p1, Position from, Position to) {

		if (from.distance(to) == 1
				|| (from.getDirection(to).isDiagonal() && from.distance(to) == 2))
			return false;

		for (final Position pos : CachedLines.supercover(from, to)) {
			if (pos.equals(from) || pos.equals(to))
				continue;

			if (units[pos.x][pos.y] != null
					&& units[pos.x][pos.y].p1Owner != p1
					&& units[pos.x][pos.y].hp != 0)
				return true;
		}

		return false;

	}

	private void dropInferno(Position to) throws Exception {

		for (int x = -1; x <= 1; x++)
			for (int y = -1; y <= 1; y++) {
				if (to.x + x < 0 || to.x + x >= map.width || to.y + y < 0 || to.y + y >= map.height)
					continue;
				final Position pos = SingletonAction.positions[to.x + x][to.y + y];
				if (units[pos.x][pos.y] != null
						&& units[pos.x][pos.y].p1Owner != p1Turn) {
					if (units[pos.x][pos.y].hp == 0) {
						units[pos.x][pos.y] = null;
						continue;
					}
					double damage = INFERNO_DAMAGE;
					if (units[pos.x][pos.y].unitClass.card == Card.CRYSTAL) {
						final int bonus = assaultBonus();
						damage += bonus;
					}
					final double resistance = units[pos.x][pos.y].resistance(
							this, pos, AttackType.Magical);
					damage = damage * ((100d - resistance) / 100d);
					units[pos.x][pos.y].hp -= damage;
					if (units[pos.x][pos.y].hp <= 0)
						if (units[pos.x][pos.y].unitClass.card == Card.CRYSTAL) {
							checkWinOnCrystals(p1Turn ? 2 : 1);
							units[pos.x][pos.y] = null;
						} else {
							units[pos.x][pos.y].hp = 0;
							checkWinOnUnits(p1Turn ? 2 : 1);
						}
				}
			}

		currentHand().remove(Card.INFERNO);
		APLeft--;

	}

	private void attack(Unit attacker, Position attPos, Unit defender, Position defPos) throws Exception {
		if (attacker.unitClass.attack == null)
			return;
		if (defender.hp == 0) {
			units[defPos.x][defPos.y] = null;
			move(attacker, attPos, defPos);
			checkWinOnUnits(p1Turn ? 2 : 1);
		} else {
			int damage = attacker.damage(this, attPos, defender, defPos);
			if (defender.unitClass.card == Card.CRYSTAL) {
				final int bonus = assaultBonus();
				damage += bonus;
			}
			defender.hp -= damage;
			if (defender.hp <= 0) {
				defender.hp = 0;
				if (defender.unitClass.card == Card.CRYSTAL) {
					units[defPos.x][defPos.y] = null;
					checkWinOnCrystals(p1Turn ? 2 : 1);
				} else {
					units[defPos.x][defPos.y].hp = 0;
					checkWinOnUnits(p1Turn ? 2 : 1);
				}
			}
			if (attacker.unitClass.attack.push)
				push(defender, attPos, defPos);
			if (attacker.unitClass.attack.chain)
				chain(attacker,
						attPos,
						defPos,
						Direction.direction(defPos.x - attPos.x, defPos.y
								- attPos.y), 1);
		}
		attacker.equipment.remove(Card.SCROLL);
		APLeft--;
	}

	private void chain(Unit attacker, Position attPos, Position from,
			Direction dir, int jump) throws Exception {

		if (jump >= 3)
			return;

		final Position bestPos = nextJump(from, dir);

		// Attack
		if (bestPos != null) {
			chainTargets.add(bestPos);
			int damage = attacker.damage(this, attPos,
					units[bestPos.x][bestPos.y], bestPos);
			if (jump == 1)
				damage = (int) (damage * 0.75);
			else if (jump == 2)
				damage = (int) (damage * 0.56);
			else
				System.out.println("Illegal number of jumps!");

			if (units[bestPos.x][bestPos.y].unitClass.card == Card.CRYSTAL)
				damage += assaultBonus();

			units[bestPos.x][bestPos.y].hp -= damage;
			if (units[bestPos.x][bestPos.y].hp <= 0) {
				units[bestPos.x][bestPos.y].hp = 0;
				if (units[bestPos.x][bestPos.y].unitClass.card == Card.CRYSTAL) {
					checkWinOnCrystals(p1Turn ? 2 : 1);
					units[bestPos.x][bestPos.y] = null;
				} else {
					units[bestPos.x][bestPos.y].hp = 0;
					checkWinOnUnits(p1Turn ? 2 : 1);
				}
			}
			chain(attacker, attPos, bestPos, from.getDirection(bestPos),
					jump + 1);
		}

	}

	private Position nextJump(Position from, Direction dir) {

		int bestValue = 0;
		Position bestPos = null;

		// Find best target
		for (int newDirX = -1; newDirX <= 1; newDirX++)
			for (int newDirY = -1; newDirY <= 1; newDirY++) {
				if (newDirX == 0 && newDirY == 0)
					continue;

				if (from.x + newDirX < 0 || from.x + newDirX >= map.width || from.y + newDirY < 0 || from.y + newDirY >= map.height)
					continue;
				
				final Position newPos = SingletonAction.positions[from.x + newDirX][from.y + newDirY];
				
				if (units[newPos.x][newPos.y] != null
						&& units[newPos.x][newPos.y].p1Owner != p1Turn
						&& units[newPos.x][newPos.y].hp > 0) {

					final Direction newDir = Direction.direction(newDirX,
							newDirY);

					if (newDir.opposite(dir))
						continue;

					final int chainValue = chainValue(dir, newDir);

					if (chainValue > bestValue) {
						bestPos = newPos;
						bestValue = chainValue;
					}
				}
			}

		return bestPos;
	}

	private int chainValue(Direction dir, Direction newDir) {

		if (dir.equals(newDir))
			return 10;

		int value = 1;

		if (!newDir.isDiagonal())
			value += 4;

		if (newDir.isNorth())
			value += 2;

		if (newDir.isEast())
			value += 1;

		return value;

	}

	private int assaultBonus() {
		int bonus = 0;
		for (final Position pos : map.assaultSquares)
			if (units[pos.x][pos.y] != null
					&& units[pos.x][pos.y].p1Owner == p1Turn
					&& units[pos.x][pos.y].hp != 0)
				bonus += ASSAULT_BONUS;
		return bonus;
	}

	private void checkWinOnUnits(int p) {

		if (!aliveOnUnits(p))
			isTerminal = true;

	}

	private void checkWinOnCrystals(int p) {

		if (!aliveOnCrystals(p))
			isTerminal = true;

	}

	public int getWinner() {

		if (turn >= TURN_LIMIT)
			return 0;
		
		boolean p1Alive = true;
		boolean p2Alive = true;

		if (!aliveOnCrystals(1) || !aliveOnUnits(1))
			p1Alive = false;

		if (!aliveOnCrystals(2) || !aliveOnUnits(2))
			p2Alive = false;

		if (p1Alive == p2Alive)
			return 0;

		if (p1Alive)
			return 1;

		if (p2Alive)
			return 2;

		return 0;

	}

	private boolean aliveOnCrystals(int player) {

		for (final Position pos : crystals(player))
			if (units[pos.x][pos.y] != null
					&& units[pos.x][pos.y].unitClass.card == Card.CRYSTAL
					&& units[pos.x][pos.y].hp > 0)
				return true;

		return false;

	}

	private List<Position> crystals(int player) {
		if (player == 1)
			return map.p1Crystals;
		if (player == 2)
			return map.p2Crystals;
		return null;
	}

	private boolean aliveOnUnits(int player) {

		if (deck(player).hasUnits() || hand(player).hasUnits())
			return true;

		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null && units[x][y].p1Owner == (player == 1)
						&& units[x][y].unitClass.card != Card.CRYSTAL)
					return true;

		return false;

	}

	private CardSet deck(int player) {
		if (player == 1)
			return p1Deck;
		if (player == 2)
			return p2Deck;
		return null;
	}

	private CardSet hand(int player) {
		if (player == 1)
			return p1Hand;
		if (player == 2)
			return p2Hand;
		return null;
	}

	private void push(Unit defender, Position attPos, Position defPos)
			throws Exception {

		if (defender.unitClass.card == Card.CRYSTAL)
			return;

		int x = 0;
		int y = 0;

		if (attPos.x > defPos.x)
			x = -1;
		if (attPos.x < defPos.x)
			x = 1;
		if (attPos.y > defPos.y)
			y = -1;
		if (attPos.y < defPos.y)
			y = 1;

		if (defPos.x + x >= map.width || defPos.x + x < 0 || defPos.y + y >= map.height || defPos.y + y < 0)
			return;

		final Position newPos = SingletonAction.positions[defPos.x + x][defPos.y + y];
		
		if (units[newPos.x][newPos.y] != null
				&& units[newPos.x][newPos.y].hp > 0)
			return;

		if (map.squareAt(newPos) == SquareType.DEPLOY_1 && !defender.p1Owner)
			return;

		if (map.squareAt(newPos) == SquareType.DEPLOY_2 && defender.p1Owner)
			return;

		if (units[defPos.x][defPos.y] != null) {
			units[defPos.x][defPos.y] = null;
		}

		units[newPos.x][newPos.y] = defender;

	}

	private void heal(Unit healer, Position pos, Unit unitTo) {

		int power = healer.power(this, pos);

		if (unitTo.hp == 0)
			power *= healer.unitClass.heal.revive;
		else
			power *= healer.unitClass.heal.heal;
		
		unitTo.heal(power);
		healer.equipment.remove(Card.SCROLL);
		
		APLeft--;

	}

	private void swap(Unit unitFrom, Position from, Unit unitTo, Position to) {
		units[from.x][from.y] = unitTo;
		units[to.x][to.y] = unitFrom;
		APLeft--;
	}

	private void move(Unit unit, Position from, Position to) throws Exception {
		units[from.x][from.y] = null;
		units[to.x][to.y] = unit;
		APLeft--;
	}

	private void equip(Card card, Position pos) {
		if (card == Card.REVIVE_POTION) {
			if (units[pos.x][pos.y].hp == 0)
				units[pos.x][pos.y].heal(POTION_REVIVE);
			else
				units[pos.x][pos.y].heal(POTION_HEAL);
		} else
			units[pos.x][pos.y].equip(card, this);
		currentHand().remove(card);
		APLeft--;
	}

	private void deploy(Card card, Position pos) {
		units[pos.x][pos.y] = new Unit(card, p1Turn);
		currentHand().remove(card);
		APLeft--;
	}

	private void endTurn() throws Exception {
		removeDying(p1Turn);
		checkWinOnUnits(1);
		checkWinOnUnits(2);
		checkWinOnCrystals(p1Turn ? 2 : 1);
		if (turn >= TURN_LIMIT)
			isTerminal = true;
		if (!isTerminal) {
			drawCards();
			p1Turn = !p1Turn;
			drawCards();
			APLeft = ACTION_POINTS;
			turn++;
		}
		
	}

	public void dealCards() {
		while (!legalStartingHand(p1Hand))
			dealCards(1);

		while (!legalStartingHand(p2Hand))
			dealCards(2);
	}

	private void dealCards(int player) {
		if (player == 1) {
			p1Deck.addAll(p1Hand);
			p1Hand.clear();
			// Collections.shuffle(p1Deck);
			drawHandFrom(p1Deck, p1Hand);
		} else if (player == 2) {
			p2Deck.addAll(p2Hand);
			p2Hand.clear();
			// Collections.shuffle(p2Hand);
			drawHandFrom(p2Deck, p2Hand);
		}

	}

	private boolean legalStartingHand(CardSet hand) {
		if (hand.size != 6)
			return false;

		if (hand.units() == REQUIRED_UNITS)
			return true;

		return false;

	}

	private void drawHandFrom(CardSet deck, CardSet hand) {

		Card card = null;
		while (!deck.isEmpty() && hand.size < 6) {
			if (RANDOMNESS)
				card = deck.random();
			else
				card = deck.determined();
			hand.add(card);
			deck.remove(card);
		}

	}

	private void drawCards() {

		drawHandFrom(currentDeck(), currentHand());

	}

	public CardSet currentHand() {
		if (p1Turn)
			return p1Hand;
		return p2Hand;
	}

	public CardSet currentDeck() {
		if (p1Turn)
			return p1Deck;
		return p2Deck;
	}

	public void removeDying(boolean p1) throws Exception {
		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null && units[x][y].p1Owner == p1
						&& units[x][y].hp == 0) {
					units[x][y] = null;
				}
	}

	public GameState copy() {
		final Unit[][] un = new Unit[map.width][map.height];
		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null)
					un[x][y] = units[x][y].copy();
		final CardSet p1h = new CardSet(p1Hand.seed);
		p1h.imitate(p1Hand);
		final CardSet p2h = new CardSet(p2Hand.seed);
		p2h.imitate(p2Hand);
		final CardSet p1d = new CardSet(p1Deck.seed);
		p1d.imitate(p1Deck);
		final CardSet p2d = new CardSet(p2Deck.seed);
		p2d.imitate(p2Deck);

		return new GameState(map, p1Turn, turn, APLeft, un, p1h, p2h, p1d, p2d,
				chainTargets, isTerminal);
	}

	public void imitate(GameState state) {

		map = state.map;
		if (units.length != state.units.length
				|| (state.units.length > 0 && units[0].length != state.units[0].length))
			units = new Unit[map.width][map.height];

		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++) {
				if (units[x][y] != null) 
					units[x][y] = null;
				if (state.units[x][y] != null) {
					units[x][y] = new Unit(null, false);
					units[x][y].imitate(state.units[x][y]);
				}
			}
		p1Hand.imitate(state.p1Hand);
		p2Hand.imitate(state.p2Hand);
		p1Deck.imitate(state.p1Deck);
		p2Deck.imitate(state.p2Deck);
		isTerminal = state.isTerminal;
		p1Turn = state.p1Turn;
		turn = state.turn;
		APLeft = state.APLeft;
		map = state.map;
		// chainTargets.clear();
		// chainTargets.addAll(state.chainTargets); // NOT NECESSARY

	}
	
	public long simpleHash() {
		final int prime = 1193;
		long result = 1;
		result = prime * result + (isTerminal ? 0 : 1);
		result = prime * result + p1Hand.hashCode();
		result = prime * result + p2Deck.hashCode();
		result = prime * result + p2Hand.hashCode();
		result = prime * result + p1Deck.hashCode();

		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null)
					result = prime * result + units[x][y].hash(x, y);

		return result;
	}

	public long hash() {
		final int prime = 1193;
		long result = 1;
		result = prime * result + APLeft;
		result = prime * result + turn;
		result = prime * result + (isTerminal ? 0 : 1);
		result = prime * result + p1Hand.hashCode();
		result = prime * result + p2Deck.hashCode();
		result = prime * result + p2Hand.hashCode();
		result = prime * result + p1Deck.hashCode();

		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null)
					result = prime * result + units[x][y].hash(x, y);

		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final GameState other = (GameState) obj;
		if (APLeft != other.APLeft)
			return false;
		if (isTerminal != other.isTerminal)
			return false;
		if (map == null) {
			if (other.map != null)
				return false;
		} else if (!map.equals(other.map))
			return false;
		if (p1Deck == null) {
			if (other.p1Deck != null)
				return false;
		} else if (!p1Deck.equals(other.p1Deck))
			return false;
		if (p1Hand == null) {
			if (other.p1Hand != null)
				return false;
		} else if (!p1Hand.equals(other.p1Hand))
			return false;
		if (p1Turn != other.p1Turn)
			return false;
		if (p2Deck == null) {
			if (other.p2Deck != null)
				return false;
		} else if (!p2Deck.equals(other.p2Deck))
			return false;
		if (p2Hand == null) {
			if (other.p2Hand != null)
				return false;
		} else if (!p2Hand.equals(other.p2Hand))
			return false;
		if (!Arrays.deepEquals(units, other.units))
			return false;
		if (turn != other.turn)
			return false;
		return true;
	}

	public void print() {
		final List<Unit> p1Units = new ArrayList<Unit>();
		final List<Unit> p2Units = new ArrayList<Unit>();
		for (int y = 0; y < units[0].length; y++) {
			System.out.print("|");
			for (int x = 0; x < units.length; x++)
				if (units[x][y] == null)
					System.out.print("__|");
				else {
					System.out.print(units[x][y].unitClass.card.name()
							.substring(0, 2) + "|");
					if (units[x][y].p1Owner)
						p1Units.add(units[x][y]);
					else
						p2Units.add(units[x][y]);
				}
			System.out.print("\n");
		}
		System.out.println(p1Hand);
		for (final Unit unit : p1Units)
			System.out.println(unit);
		System.out.println(p2Hand);
		for (final Unit unit : p2Units)
			System.out.println(unit);

	}

	public SquareType squareAt(Position pos) {
		return map.squares[pos.x][pos.y];
	}

	public Unit unitAt(Position pos) {
		return units[pos.x][pos.y];
	}

	public Unit unitAt(int x, int y) {
		return units[x][y];
	}

	public int cardsLeft(int p) {
		if (p == 1)
			return p1Deck.size + p1Hand.size;
		else if (p == 2)
			return p2Deck.size + p2Hand.size;
		return -1;
	}

	public void returnUnits() {
		for (int x = 0; x < map.width; x++)
			for (int y = 0; y < map.height; y++)
				if (units[x][y] != null) 
					units[x][y] = null;
	}

	public void hideCards(boolean p1) {
		if (p1){
			for(Card card : Card.values())
				for(int i = 0; i < p1Hand.count(card); i++)
					p1Deck.add(card);
			p1Hand.clear();
		} else {
			for(Card card : Card.values())
				for(int i = 0; i < p2Hand.count(card); i++)
					p2Deck.add(card);
			p2Hand.clear();
		}
	}

} public class ImageLib {

	public static Map<String, BufferedImage> lib;

	static {
		
		String basePath = PathHelper.basePath();
		
		System.out.println("BasePath = " + basePath);

		lib = new HashMap<String, BufferedImage>();
		try {
			lib.put("crystal-1",
					ImageIO.read(new File(basePath + "/img/crystal-1.png")));
			lib.put("crystal-2",
					ImageIO.read(new File(basePath + "/img/crystal-2.png")));
			lib.put("door-1",
					ImageIO.read(new File(basePath + "/img/door-1.png")));
			lib.put("door-2",
					ImageIO.read(new File(basePath + "/img/door-2.png")));
			lib.put("go-active",
					ImageIO.read(new File(basePath + "/img/go-active.png")));
			lib.put("go-inactive",
					ImageIO.read(new File(basePath + "/img/go-inactive.png")));
			lib.put("ap-0", ImageIO.read(new File(basePath + "/img/ap-0.png")));
			lib.put("ap-1", ImageIO.read(new File(basePath + "/img/ap-1.png")));
			lib.put("ap-2", ImageIO.read(new File(basePath + "/img/ap-2.png")));
			lib.put("ap-3", ImageIO.read(new File(basePath + "/img/ap-3.png")));
			lib.put("ap-4", ImageIO.read(new File(basePath + "/img/ap-4.png")));
			lib.put("ap-5", ImageIO.read(new File(basePath + "/img/ap-5.png")));
			lib.put("header",
					ImageIO.read(new File(basePath + "/img/header.png")));
			lib.put("bar", ImageIO.read(new File(basePath + "/img/bar.png")));
			lib.put("assault",
					ImageIO.read(new File(basePath + "/img/assault.png")));
			lib.put("defense",
					ImageIO.read(new File(basePath + "/img/defense.png")));
			lib.put("power",
					ImageIO.read(new File(basePath + "/img/power.png")));
			lib.put("deploy-1",
					ImageIO.read(new File(basePath + "/img/deploy-1.png")));
			lib.put("deploy-2",
					ImageIO.read(new File(basePath + "/img/deploy-2.png")));
			lib.put("header",
					ImageIO.read(new File(basePath + "/img/header.png")));
			lib.put("bar", ImageIO.read(new File(basePath + "/img/bar.png")));

			lib.put("knight-1",
					ImageIO.read(new File(basePath + "/img/knight-1.png")));
			lib.put("archer-1",
					ImageIO.read(new File(basePath + "/img/archer-1.png")));
			lib.put("cleric-1",
					ImageIO.read(new File(basePath + "/img/cleric-1.png")));
			lib.put("wizard-1",
					ImageIO.read(new File(basePath + "/img/wizard-1.png")));
			lib.put("ninja-1",
					ImageIO.read(new File(basePath + "/img/ninja-1.png")));

			lib.put("knight-2",
					ImageIO.read(new File(basePath + "/img/knight-2.png")));
			lib.put("archer-2",
					ImageIO.read(new File(basePath + "/img/archer-2.png")));
			lib.put("cleric-2",
					ImageIO.read(new File(basePath + "/img/cleric-2.png")));
			lib.put("wizard-2",
					ImageIO.read(new File(basePath + "/img/wizard-2.png")));
			lib.put("ninja-2",
					ImageIO.read(new File(basePath + "/img/ninja-2.png")));

			lib.put("scroll-1",
					ImageIO.read(new File(basePath + "/img/scroll-1.png")));
			lib.put("helmet-1",
					ImageIO.read(new File(basePath + "/img/helmet-1.png")));
			lib.put("scroll-2",
					ImageIO.read(new File(basePath + "/img/scroll-2.png")));
			lib.put("helmet-2",
					ImageIO.read(new File(basePath + "/img/helmet-2.png")));

			lib.put("knight-red-1",
					ImageIO.read(new File(basePath + "/img/knight-red-1.png")));
			lib.put("archer-red-1",
					ImageIO.read(new File(basePath + "/img/archer-red-1.png")));
			lib.put("cleric-red-1",
					ImageIO.read(new File(basePath + "/img/cleric-red-1.png")));
			lib.put("wizard-red-1",
					ImageIO.read(new File(basePath + "/img/wizard-red-1.png")));
			lib.put("ninja-red-1",
					ImageIO.read(new File(basePath + "/img/ninja-red-1.png")));

			lib.put("knight-red-2",
					ImageIO.read(new File(basePath + "/img/knight-red-2.png")));
			lib.put("archer-red-2",
					ImageIO.read(new File(basePath + "/img/archer-red-2.png")));
			lib.put("cleric-red-2",
					ImageIO.read(new File(basePath + "/img/cleric-red-2.png")));
			lib.put("wizard-red-2",
					ImageIO.read(new File(basePath + "/img/wizard-red-2.png")));
			lib.put("ninja-red-2",
					ImageIO.read(new File(basePath + "/img/ninja-red-2.png")));

			lib.put("crystal-red-1",
					ImageIO.read(new File(basePath + "/img/crystal-1.png")));
			lib.put("crystal-red-2",
					ImageIO.read(new File(basePath + "/img/crystal-2.png")));

			lib.put("sword",
					ImageIO.read(new File(basePath + "/img/sword.png")));
			lib.put("shield",
					ImageIO.read(new File(basePath + "/img/shield.png")));
			lib.put("potion",
					ImageIO.read(new File(basePath + "/img/potion.png")));
			lib.put("inferno",
					ImageIO.read(new File(basePath + "/img/inferno.png")));

			lib.put("scroll-small-1", ImageIO.read(new File(basePath
					+ "/img/scroll-small-1.png")));
			lib.put("helmet-small-1", ImageIO.read(new File(basePath
					+ "/img/helmet-small-1.png")));
			lib.put("scroll-small-2", ImageIO.read(new File(basePath
					+ "/img/scroll-small-2.png")));
			lib.put("helmet-small-2", ImageIO.read(new File(basePath
					+ "/img/helmet-small-2.png")));
			lib.put("sword-small",
					ImageIO.read(new File(basePath + "/img/sword-small.png")));
			lib.put("shield-small",
					ImageIO.read(new File(basePath + "/img/shield-small.png")));
			
			lib.put("beginner",
					ImageIO.read(new File(basePath + "/img/beginner.png")));
			
			lib.put("intermediate",
					ImageIO.read(new File(basePath + "/img/intermediate.png")));
			
			lib.put("expert",
					ImageIO.read(new File(basePath + "/img/expert.png")));
			
			lib.put("play-again",
					ImageIO.read(new File(basePath + "/img/play-again.png")));
			
			lib.put("play",
					ImageIO.read(new File(basePath + "/img/play.png")));

		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

} public class UnitClassLib {

	public static HashMap<Card, UnitClass> lib = new HashMap<Card, UnitClass>();

	static {

		// Add units
		lib.put(Card.KNIGHT, new UnitClass(Card.KNIGHT, 1000, 2, 200, 20, 0,
				null, null, false));
		lib.put(Card.ARCHER, new UnitClass(Card.ARCHER, 800, 2, 300, 0, 0,
				null, null, false));
		lib.put(Card.CLERIC, new UnitClass(Card.CLERIC, 800, 2, 200, 0, 0,
				null, null, false));
		lib.put(Card.WIZARD, new UnitClass(Card.WIZARD, 800, 2, 200, 0, 10,
				null, null, false));
		lib.put(Card.NINJA, new UnitClass(Card.NINJA, 800, 3, 200, 0, 0, null,
				null, false));

		// Add crystal
		lib.put(Card.CRYSTAL, new UnitClass(Card.CRYSTAL, 4500, 0, 0, 0, 0,
				null, null, false));

		// Add attacks
		lib.get(Card.KNIGHT).attack = new Attack(1, AttackType.Physical, 200,
				1, 1, false, true);
		lib.get(Card.ARCHER).attack = new Attack(3, AttackType.Physical, 300,
				0.5, 1, false, false);
		lib.get(Card.CLERIC).attack = new Attack(2, AttackType.Magical, 200, 1,
				1, false, false);
		lib.get(Card.WIZARD).attack = new Attack(2, AttackType.Magical, 200, 1,
				1, true, false);
		lib.get(Card.NINJA).attack = new Attack(2, AttackType.Physical, 200, 2,
				1, false, false);

		// Add heal
		lib.get(Card.CLERIC).heal = new Heal(2, 3, 2);
		lib.get(Card.NINJA).swap = true;

	}

} public enum AttackType {

	Physical, Magical;
	
} public enum Card {

	KNIGHT(CardType.UNIT), 
	ARCHER(CardType.UNIT), 
	CLERIC(CardType.UNIT), 
	WIZARD(CardType.UNIT), 
	NINJA(CardType.UNIT),
	INFERNO(CardType.SPELL), 
	REVIVE_POTION(CardType.ITEM), 
	RUNEMETAL(CardType.ITEM), 
	SCROLL(CardType.ITEM), 
	DRAGONSCALE(CardType.ITEM), 
	SHINING_HELM(CardType.ITEM), 
	CRYSTAL(CardType.UNIT);
	
	public final CardType type;
	
	Card(CardType type) {
		this.type = type;
	}
	
} public class CardSet {

	public int[] cards;
	public int size;
	public int seed;

	public CardSet() {
		cards = new int[Card.values().length];
		size = 0;
		seed = (int) (Math.random() * 1000);
	}

	public CardSet(int seed) {
		cards = new int[Card.values().length];
		size = 0;
		this.seed = seed;
	}

	public Card determined() {
		if (seed > 100000)
			seed = (int) (seed * 0.1);
		else
			seed = (int) (seed * 1.3 + 7);
		return get(seed % size);
	}

	public Card random() {
		return get((int) Math.floor(Math.random() * size));
	}

	public Card get(Integer r) {
		int c = 0;
		int i = 0;
		while (true) {
			c += cards[i];
			if (c > r)
				break;
			i++;
		}
		return Card.values()[i];
	}

	public void add(Card card) {
		cards[card.ordinal()]++;
		size++;
	}

	public void remove(Card card) {
		if (cards[card.ordinal()] > 0) {
			cards[card.ordinal()]--;
			size--;
		}
	}

	public boolean hasUnits() {
		if (units() > 0)
			return true;
		return false;
	}

	public void addAll(CardSet other) {
		size += other.size;
		for (int i = 0; i < other.cards.length; i++)
			cards[i] += other.cards[i];
	}

	public void clear() {
		size = 0;
		Arrays.fill(cards, 0);
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public void imitate(CardSet p1Hand) {
		clear();
		addAll(p1Hand);
		seed = p1Hand.seed;
	}

	public int units() {
		int units = 0;
		for (final Card card : Card.values())
			if (card.type == CardType.UNIT && card != Card.CRYSTAL)
				units += cards[card.ordinal()];
		return units;
	}
	

	public boolean has(Card card) {
		if (cards.length > card.ordinal())
			return cards[card.ordinal()] > 0 ? true : false;
		return false;
	}

	public boolean contains(Card card) {
		if (cards.length > card.ordinal())
			return cards[card.ordinal()] > 0;
		return false;
	}

	public int count(Card card) {
		if (cards.length > card.ordinal())
			return cards[card.ordinal()];
		return 0;
	}

	@Override
	public int hashCode() {
		int hash = 1;
		final int prime = 7;
		for (int i = 0; i < cards.length; i++)
			hash = hash * prime + cards[i];
		return hash;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final CardSet other = (CardSet) obj;
		if (!Arrays.equals(cards, other.cards))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return Arrays.toString(cards).replaceAll(" ", "");
	}


} public enum CardType {
	UNIT, 
	ITEM,
	SPELL;
} public class Position {

	public int x;
	public int y;

	public Position(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public Position() {
		super();
		x = 0;
		y = 0;
	}

	public Direction getDirection(Position pos) {
		if (pos == null)
			return null;
		return Direction.direction(pos.x - x, pos.y - y);
	}

	public int hashCode() {
		int result = 1;
		result = 5 * result + x;
		result = 5 * result + y;
		return result;
	}

	@Override
	public String toString() {
		return "(" + x + "," + y + ")";
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final Position other = (Position) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

	public int distance(Position to) {
		int xx = x - to.x;
		if (xx < 0)
			xx = xx * (-1);
		int yy = y - to.y;
		if (yy < 0)
			yy = yy * (-1);
		return xx + yy;
	}

} public enum SquareType {

	NONE, ASSAULT, DEFENSE, POWER, DEPLOY_1, DEPLOY_2;
	
} public class Unit {

	public int hp;
	public UnitClass unitClass;
	public Set<Card> equipment;
	public boolean p1Owner;
	public static int count = 0;

	public Unit(Card type, boolean p1Owner) {
		super();
		/*
		count++;
		if (count % 1000 == 0)
			System.out.println(count);
		*/
		if (type != null) {
			unitClass = UnitClassLib.lib.get(type);
			hp = unitClass.maxHP;
		}
		equipment = new HashSet<Card>();
		this.p1Owner = p1Owner;
	}

	public Unit(int hp, Unit type, boolean p1Owner, Set<Card> equipment) {
		super();
		// System.out.println(count++);
		unitClass = UnitClassLib.lib.get(type);
		this.hp = hp;
		this.equipment = equipment;
		this.p1Owner = p1Owner;
	}

	public Unit(int hp, UnitClass uclass, boolean p1Owner, Set<Card> equipment) {
		super();
		// System.out.println(count++);
		unitClass = uclass;
		this.hp = hp;
		this.equipment = equipment;
		this.p1Owner = p1Owner;
	}

	public void init(Card type, boolean p1Owner) {
		if (type != null) {
			unitClass = UnitClassLib.lib.get(type);
			hp = unitClass.maxHP;
		}
		equipment = new HashSet<Card>();
		this.p1Owner = p1Owner;
	}

	public void equip(Card card, GameState state) {
		if (card == Card.REVIVE_POTION) {
			if (hp == 0)
				hp += 100;
			else
				hp += 600;
		} else {
			equipment.add(card);
			if (card == Card.DRAGONSCALE || card == Card.SHINING_HELM)
				hp += hp / 10d;
		}
		hp = Math.min(hp, maxHP());
	}

	public int power(GameState state, Position pos) {

		// Initial power
		int power = unitClass.power;
		
		// Sword
		if (equipment.contains(Card.RUNEMETAL))
			power += power / 2;

		// Power boost
		if (state.map.squareAt(pos.x, pos.y) == SquareType.POWER)
			power += 100;

		// Scroll
		if (equipment.contains(Card.SCROLL))
			power *= 3;

		return power;
	}

	public int damage(GameState state, Position attPos, Unit defender,
			Position defPos) {

		if (unitClass.attack == null)
			return 0;
		
		double dam = power(state, attPos);

		if (attPos.distance(defPos) == 1)
			dam *= unitClass.attack.meleeMultiplier;
		else
			dam *= unitClass.attack.rangeMultiplier;

		final double resistance = defender.resistance(state, defPos,
				unitClass.attack.attackType);

		return (int) (dam * ((100d - resistance) / 100d));
	}

	public int maxHP() {

		int max = unitClass.maxHP;

		if (equipment.contains(Card.DRAGONSCALE))
			max += unitClass.maxHP / 10;
		if (equipment.contains(Card.SHINING_HELM))
			max += unitClass.maxHP / 10;

		return max;
	}

	public int resistance(GameState state, Position pos, AttackType attackType) {

		int res = 0;

		if (attackType == AttackType.Magical) {
			res += unitClass.magicalResistance;
			if (equipment.contains(Card.SHINING_HELM))
				res += 20;
			// TODO : also +20 for defense square?
		} else if (attackType == AttackType.Physical) {
			res += unitClass.physicalResistance;
			if (equipment.contains(Card.DRAGONSCALE))
				res += 20;
			if (state.map.squareAt(pos.x, pos.y) == SquareType.DEFENSE)
				res += 20;
		}

		return res;

	}

	public boolean fullHealth() {
		if (hp == maxHP())
			return true;
		return false;
	}

	public void heal(int health) {

		hp = Math.min(hp + health, this.maxHP());

	}

	public void imitate(Unit unit) {
		hp = unit.hp;
		unitClass = unit.unitClass;
		p1Owner = unit.p1Owner;
		equipment.clear();
		equipment.addAll(unit.equipment);
	}

	public Unit copy() {
		final Set<Card> eq = new HashSet<Card>();
		for (final Card card : equipment)
			eq.add(card);

		return new Unit(hp, unitClass, p1Owner, eq);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final Unit other = (Unit) obj;
		if (equipment == null) {
			if (other.equipment != null)
				return false;
		} else if (!equipment.equals(other.equipment))
			return false;
		if (hp != other.hp)
			return false;
		if (p1Owner != other.p1Owner)
			return false;
		if (unitClass == null) {
			if (other.unitClass != null)
				return false;
		} else if (!unitClass.equals(other.unitClass))
			return false;
		return true;
	}
	
	public int hash(int x, int y) {
		int result = 1;
		result = 16 * result + equipmentHash();
		result = 6553 * result + hp;
		result = 2 * result + (p1Owner ? 0 : 1);
		result = 6 * result + unitClass.hash();
		result = 5 * result + x;
		result = 5 * result + y;
		return result;
	}
	
	public int hash() {
		int result = 1;
		result = 16 * result + equipmentHash();
		result = 1500 * result + hp;
		result = 2 * result + (p1Owner ? 0 : 1);
		result = 6 * result + unitClass.hash();
		return result;
	}

	@Override
	public String toString() {
		return "[" + (p1Owner ? 1 : 2) + "," + unitClass.card.name() + "," + hp + "," + equipment + "]";
	}

	private int equipmentHash() {
		int e = 0;
		if (equipment.contains(Card.DRAGONSCALE))
			e += 1;
		if (equipment.contains(Card.RUNEMETAL))
			e += 2;
		if (equipment.contains(Card.SHINING_HELM))
			e += 4;
		if (equipment.contains(Card.SCROLL))
			e += 8;
		return e;
	}
} public abstract class Action {
	
} public class DropAction extends Action {

	public Card type;
	public Position to;

	public DropAction(Card type, Position to) {
		super();
		this.type = type;
		this.to = to;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((to == null) ? 0 : to.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final DropAction other = (DropAction) obj;
		if (to == null) {
			if (other.to != null)
				return false;
		} else if (!to.equals(other.to))
			return false;
		if (type != other.type)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "DropAction [type=" + type + ", to=" + to + "]";
	}

} public class EndTurnAction extends Action {

	@Override
	public String toString() {
		return "EndTurnAction []";
	}
	
} public class SwapCardAction extends Action {

	public Card card;

	public SwapCardAction(Card card) {
		super();
		this.card = card;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SwapCardAction other = (SwapCardAction) obj;
		if (card != other.card)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "SwapCardAction [card=" + card.name() + "]";
	}

} public class UnitAction extends Action {

	public Position from;
	public Position to;
	public UnitActionType type;

	public UnitAction(Position from, Position to, UnitActionType type) {
		super();
		this.from = from;
		this.to = to;
		this.type = type;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		UnitAction other = (UnitAction) obj;
		if (from == null) {
			if (other.from != null)
				return false;
		} else if (!from.equals(other.from))
			return false;
		if (to == null) {
			if (other.to != null)
				return false;
		} else if (!to.equals(other.to))
			return false;
		if (type != other.type)
			return false;
		return true;
	}



	@Override
	public String toString() {
		return "UnitAction [from=" + from + ", to=" + to + "]";
	}

} public enum UnitActionType {
	ATTACK, MOVE, HEAL, SWAP;
}
import game.GameState;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
import libs.ImageLib;
import libs.UnitClassLib;
import model.AttackType;
import model.Card;
import model.CardSet;
import model.CardType;
import model.Position;
import model.SquareType;
import model.Unit;
import action.Action;
import action.DropAction;
import action.EndTurnAction;
import action.SwapCardAction;
import action.UnitAction;
import action.UnitActionType;
package ui;





public class UI extends JComponent {

	public JFrame frame;
	public int width;
	public int height;
	public int squareSize = 64;
	public GameState state;
	public Action lastAction;
	private final int bottom;
	private final InputController inputController;
	public Action action;
	private final String p1Name;
	private final String p2Name;
	private final Boolean p1Human;
	private final Boolean p2Human;
	private int turn = 0;
	
	public List<Action> actionLayer;
	public boolean connection;

	public UI(GameState state, boolean p1Human, boolean p2Human, String p1Name, String p2Name) {
		connection = true;
		frame = new JFrame();
		width = Math.max(8, state.map.width) * squareSize + squareSize * 2;
		height = state.map.height * squareSize + squareSize * 2 + squareSize;
		frame.setSize(width, height);
		frame.setTitle("Hero AI");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(this);
		frame.setVisible(true);
		this.p1Human = p1Human;
		this.p2Human = p2Human;
		this.p1Name = p1Name;
		this.p2Name = p2Name;
		inputController = new InputController(p1Human, p2Human, squareSize,
				squareSize, squareSize, this);
		this.addMouseListener(inputController);
		this.addMouseMotionListener(inputController);
		this.state = state;
		bottom = squareSize + state.map.height * squareSize + squareSize / 4;
		actionLayer = new ArrayList<Action>();
		repaint();
	}

	public void resetActions() {
		inputController.reset();
		action = null;
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);

		if (state == null)
			return;
		
		if (!connection){
			g.setColor(Color.darkGray);
			g.fillRect(0, 0, width, height);
			g.setColor(Color.lightGray);
			g.setFont(new Font("Arial", 1, 32));
			g.drawString("LOADING", 275, 140);
			g.setFont(new Font("Arial", 1, 12));
			g.drawString("If this takes more than a few seconds, check your internet connnection.", 142, 280);
			return;
		}
		
		if (state.turn != turn){
			turn = state.turn;
			frame.setTitle("Hero AI - Turn " + turn);
		}
		
		if (state == null) {
			System.out.println("State is NULL");
			return;
		}

		if (inputController != null) {
			inputController.state = state.copy();
			action = inputController.action;
		}

		try {
			paintBoard(g);
			paintHeader(g);
			paintLastUnitAction(g);
			paintGameObjects(g);
			paintHP(g);
			paintInfo(g);
			paintHand(g);
			paintAP(g);
			paintGo(g);
			paintDoors(g);
			paintLastSwapCardAction(g);
			paintLastDropAction(g);
			paintWinScreen(g);
			paintUnitDetails(g);
			paintDeck(g);
			paintActionLayer(g);
		} catch (final IOException e) {
			//e.printStackTrace();
		}

	}
	
	public void setActionLayer(List<Action> actions){
		synchronized (this) {
			actionLayer.clear();
			actionLayer.addAll(actions);
		}
	}

	private void paintActionLayer(Graphics g) {
		synchronized (this) {
			for(Action action : actionLayer){
				if (action instanceof UnitAction)
					paintUnitAction(g, (UnitAction)action);
				else if (action instanceof DropAction)
					paintDropAction(g, (DropAction)action);
				else if (action instanceof SwapCardAction)