<file_sep>package core;

import java.util.Random;

import exception.IllegalShuffleException;
import exception.InvalidCardException;
import player.Player;
import player.Team;

/**
 * Main class of the system.
 * As for now, the run() method simulates a single round.
 * @author Daan
 */
public class FullMatch {
	
	private Team t1;
	private Team t2;
	private Player[] players;
	
	/**
	 * Creates a new match between the given teams.
	 */
	public FullMatch(Team t1, Team t2) {
		this.t1 = t1;
		this.t2 = t2;
		Player[] players = {t1.getPlayer1(),t1.getPlayer2(),t2.getPlayer1(),t2.getPlayer2()};
		this.players = players;
	}

	public void run() throws InvalidCardException, IllegalShuffleException {
		
		Logger.log("========== PLAYERS ==========");
		Logger.log(players[0] + " : " + players[0].identify());
		Logger.log(players[1] + " : " + players[1].identify());
		Logger.log(players[2] + " : " + players[2].identify());
		Logger.log(players[3] + " : " + players[3].identify());
		
		Logger.log("========== TEAMS ==========");
		Logger.log("Team 1: " + t1.getPlayer1() + ", " + t1.getPlayer2());
		Logger.log("Team 2: " + t2.getPlayer1() + ", " + t2.getPlayer2());
		
		Player dealer = players[new Random().nextInt(4)];
		Deck deck = new Deck();
		deck.execute(ShuffleCommand.createRandomShuffleCommand());
		int baseMultiplier = 1;
		
		while ( Math.max(t1.getTotalScore(), t2.getTotalScore()) < 101 ) {
			Round r = new Round(t1,t2,dealer,deck,baseMultiplier);
			r.run();
		
			// score calculation
			int roundScoreTeam1 = 0;
			int roundScoreTeam2 = 0;
			if ( t1.getPoolScore() > 30 ) {
				roundScoreTeam1  = (t1.getPoolScore() - 30)*r.getMultiplier();
			} else {
				roundScoreTeam2 = (t2.getPoolScore() - 30)*r.getMultiplier();
			}
			t1.increaseTotalScore(roundScoreTeam1);
			t2.increaseTotalScore(roundScoreTeam2);
			// multiplier
			if ( roundScoreTeam1 == 0 && roundScoreTeam2 == 0 ) {
				baseMultiplier = 2;
			} else {
				baseMultiplier = 1;
			}
			
			// notify players  & log scores
			t1.getPlayer1().notifyOfRoundScore(roundScoreTeam1, roundScoreTeam2, t1.getTotalScore(), t2.getTotalScore() );
			t1.getPlayer2().notifyOfRoundScore(roundScoreTeam1, roundScoreTeam2, t1.getTotalScore(), t2.getTotalScore() );
			t2.getPlayer1().notifyOfRoundScore(roundScoreTeam2, roundScoreTeam1, t2.getTotalScore(), t1.getTotalScore() );
			t2.getPlayer2().notifyOfRoundScore(roundScoreTeam2, roundScoreTeam1, t2.getTotalScore(), t1.getTotalScore() );
			Logger.log("========== SCORES ==========");
			Logger.log("Team 1: " + t1.getTotalScore());
			Logger.log("Team 2: " + t2.getTotalScore());
			
			// recalculate the deck
			deck = new Deck(t1.getPool(),t2.getPool());
			t1.getPool().reset();
			t2.getPool().reset();
			
			// new dealer
			dealer = getSuccessor(dealer,t1,t2);
			
			// shuffle
			deck.execute(ShuffleCommand.createMultiRiffleShuffle(3));
		}
		(t1.getTotalScore() >= 101 ? t1 : t2).addMatchPoint();
		t1.resetTotalScore();
		t2.resetTotalScore();
		Logger.log("========== MATCH ENDED ==========");
		Logger.log("Team 1: " + t1.getTotalScore());
		Logger.log("Team 2: " + t2.getTotalScore());
	}
	
	private Player getSuccessor(Player player, Team team1, Team team2) {
		if ( player == team1.getPlayer1() ) {
			return team2.getPlayer1();
		} else if ( player == team1.getPlayer2() ) {
			return team2.getPlayer2();
		} else if ( player == team2.getPlayer1() ) {
			return team1.getPlayer2();
		} else {
			return team1.getPlayer1();
		}
	}

}<file_sep>package test;

import java.util.List;

import org.junit.Test;

import core.Card;
import core.Deck;
import core.ShuffleCommand;

public class DeckTest {

	@Test
	public void testShuffle() {
		double sd_acc_average = 0;
		double sd_acc_variance = 0;
		double ssg_acc_average = 0;
		double ssg_acc_variance = 0;
		final int runs = 10000;
		double[] sd = new double[runs];
		double[] ssg = new double[runs];
		
		for ( int x = 0 ; x < runs ; x++) {
			Deck d = new Deck();
			d.execute(ShuffleCommand.createMultiRiffleShuffle(7));
			// according to literature 7 riffle shuffles is enough to produce a random result
			// d.multiRiffleShuffle(7);
			// d.shuffle();
			sd[x] = getSubsequentSymbolDistance(d) / 31;
			ssg[x] = getSuitSubGroups(d);
			sd_acc_average += sd[x];
			ssg_acc_average += ssg[x];
		}
		for ( int x = 0 ; x < runs ; x++) {
			sd_acc_variance += (sd[x] - sd_acc_average / runs)*(sd[x] - sd_acc_average / runs);
			ssg_acc_variance += (ssg[x] - ssg_acc_average / runs)*(ssg[x] - ssg_acc_average / runs);
		}
		System.out.println("runs: " + runs);
		System.out.println("# Average symbol distance between subsequent cards:");
		System.out.println("average: " + sd_acc_average / runs);
		System.out.println("sqrt(variance): " + Math.sqrt(sd_acc_variance / runs));
		
		System.out.println("\n# Suit subgroups:");
		System.out.println("average: " + ssg_acc_average / runs);
		System.out.println("sqrt(variance): " + Math.sqrt(ssg_acc_variance / runs));
		
	}
	
	private double getSubsequentSymbolDistance(Deck deck) {
		List<Card> cards = deck.getCards();
		int total = 0;
		int i = 0;
		for (int j = 1 ; j < cards.size() ; j++) {
			Card a = cards.get(i);
			Card b = cards.get(j);
			total += Math.abs(a.compareTo(b));
			i = j;
		}
		return total;
	}
	
	private int getSuitSubGroups(Deck deck) {
		List<Card> cards = deck.getCards();
		int i = 0;
		int total = 1;
		for ( int j = 1 ; j < cards.size() ; j++) {
			if ( !cards.get(j).getSuit().equals(cards.get(i).getSuit())) {
				total++;
			}
			i = j;
		}
		return total;
	}

}
<file_sep>### Schrijven van een custom AI-speler ###

Je mag zoveel klassen maken als je wilt op voorwaarde dat:

- 1 van je klassen de interface **Intelligence** implementeert

#### API: ####
De eerder vermeldde klasse **Intelligence** is de required interface. Deze gebruikt het programma om
vragen te stellen aan je AI. De klasse **InformationHandle** geeft je toegang tot informatie over het speelveld,
zoals o.a. de kaarten die reeds gespeeld zijn, wat de troef is, ...

Deze klasses kunnen ( en zullen ) nog wijzigen, maar zullen een gelijkaardige functionaliteit behouden.
Er is een diagram aanwezig dat een totaalbeeld schetst. Mocht je vragen of kritiek op het
design hebben: laat me iets weten! :)

#### Taal: ####
De code is het engels geschreven.
Vertaling van Manille-termen:

- slag = trick
- troef = trump
- suit = 'kleur' van de kaart (hearts, spades, ...)
- symbol = teken van de kaart (king, jack, queen, ...)

### TESTEN VAN JE ALGORITME ###

Om je algo te testen kan je de MainGraphical klasse runnen.
( of een eigen test-klasse schrijven )

### KERNPROGRAMMA ###

Voorlopig hou ik me bezig met de kern van het programma. Gelieve dus geen bestaande
klassen aan te passen.

### VRAGEN/FOUTEN/VOORSTELLEN ###

Laat me iets weten :) Er ontbreekt nog een hoop functionaliteit, maar de kern zou er moeten zijn.
<file_sep>package player;

import java.util.ArrayList;
import java.util.List;

import core.*;

/**
 * This class allows intelligent actors to ask questions to the system.
 * <br/><br/>
 * <b>Part of the AI API:</b> custom AI's can use an object of this class (when provided)
 * to inspect certain aspects of the system.
 * @author Daan
 */
public class InformationHandle {
	
	Round round;
	Player player;
	
	public InformationHandle(Player player, Round round) {
		this.round = round;
		this.player = player;
	}
	
	/**
	 * Returns the previous trick as an array. If the current trick is the first one,
	 * null will be returned.
	 * @return an array of Cards of size 4, or null.
	 */
	public Card[] getPreviousTrick() {
		return round.getPreviousTrickClone();
	}
	
	/**
	 * Returns a (sorted) list of the cards on the field.
	 * The list will be empty if no cards were played yet.
	 * @return a List of Cards
	 */
	public List<Card> inspectField() {
		return new ArrayList<Card>(round.getField());
	}
	
	/**
	 * Checks if the given card is valid to play at this time.
	 * Returns false if the card is null.
	 * @param card
	 * @return true if it is
	 */
	public boolean isValidCard(Card card) {
		if ( card != null ) {
			return round.isValidCard(card, player);
		} else {
			return false;
		}
	}
	
	/**
	 * Inspects the current trump.
	 * When the round is no-trump, null is returned.
	 * @return a Suit
	 */
	public Suit inspectTrump() {
		return round.getTrump();
	}
	
	/**
	 * Returns the multiplier for the current round.
	 * This is the factor by which the score over 30 is multiplied
	 * at the end of the round.
	 */
	public int getMultiplier() {
		return round.getMultiplier();
	}
	
	/**
	 * Returns the players position in the current trick's order of play.
	 * This is a number ranging from 0 (first player to play a card) to 3 (last person to play a card).
	 * @return position (int)
	 */
	public int getTurn() {
		return round.getPositionInSequence(player);
	}
	
	/**
	 * Returns the dealers position in the current trick's order of play.
	 * This is a number ranging from 0 (dealer was first to play a card) to 3 (dealer is last to play a card)
	 * @return position (int)
	 */
	public int getDealerTurn() {
		return round.getPositionInSequence(round.getDealer());
	}
	
	/**
	 * Returns the current pool score for the allied team. This is the
	 * sum of the values of the cards in the tricks the allied team has won.
	 * @return integer
	 */
	public int getCurrentAllyPoolScore() {
		return round.getTeam(player).getPoolScore();
	}
	
	/**
	 * Return the current pool score for the enemy team. This is the
	 * sum of the values of the cards in the tricks the enemy team has won.
	 * @return integer
	 */
	public int getCurrentEnemyPoolScore() {
		return round.getOpposingTeam(player).getPoolScore();
	}

}
<file_sep>package core;
/**
 * Possible symbols of a card. The following is associated with each symbol:
 * <br/>- strength: strength relative to other symbols (between 0 and 7)
 * <br/>- value: amount of points the symbol is worth
 * @author <NAME>
 */
public enum Symbol implements Comparable<Symbol> {
	SEVEN 	(0,0),
	EIGHT 	(1,0),
	NINE  	(2,0),
	JACK	(3,1),
	QUEEN	(4,2),
	KING	(5,3),
	ACE		(6,4),
	MANILLE	(7,5);
	
	/**
	 * The strength of the symbol, sense of a natural order
	 */
	private int strength;
	
	/**
	 * The value of the symbol in points
	 */
	private int value;
	
	/**
	 * Constructor
	 * @param strength
	 * @param value
	 */
	Symbol(int strength, int value) {
		setStrength(strength);
		setValue(value);
	}
	
	/**********************************************************************
	 * VALUE
	 **********************************************************************/
	
	private void setValue(int value) {
		this.value = value;
	}
	
	/**
	 * Inspect the value in points.
	 */
	public int getValue() {
		return value;
	}
	
	/**
	 * Checks if this symbol has a greater value than the given symbol.
	 * @param symbol
	 * @return true if it has
	 */
	public boolean hasGreaterValueThan(Symbol symbol) {
		return this.getValue() > symbol.getValue();
	}
	
	/**********************************************************************
	 * STRENGTH
	 **********************************************************************/
	private void setStrength(int strength) {
		this.strength = strength;
	}
	
	/**
	 * Inspect the relative strength. (Number between 0 and 7)
	 */
	public int getStrength() {
		return strength;
	}
	
	/**
	 * Checks if this symbol is stronger than the given symbol.
	 * @param symbol
	 * @return true if it is
	 */
	public boolean isStrongerThan(Symbol symbol) {
		return this.getStrength() > symbol.getStrength();
	}
	
}
<file_sep>package test;

import org.junit.Test;

import player.Player;
import AI.ArtificialPlayer;
import AI.ExampleIntelligence;
import AI.daan.Deducer;
import AI.daan.MonteCarloAI;
import core.Logger;
import core.Match;
import exception.IllegalShuffleException;
import exception.InvalidCardException;

public class deducerTest {

	@Test
	public void test() throws InvalidCardException, IllegalShuffleException {		
		Logger.setPrinting(true);
		Logger.setRemember(false);
		Player tAp1 = new ArtificialPlayer("Dimitri",new MonteCarloAI());
		Player tAp2 = new ArtificialPlayer("Boris",new ExampleIntelligence());
		Player tBp1 = new ArtificialPlayer("Samson",new ExampleIntelligence());
		Player tBp2 = new ArtificialPlayer("Gert",new ExampleIntelligence());
		
		Match m = new Match(tAp1,tAp2,tBp1,tBp2);
		m.run();
	}

}
<file_sep>package core;

/**
 * Represents a card, which has a suit and a symbol.
 * 
 * @author <NAME>
 * @version 0.1
 */
public final class Card implements Comparable<Card> {
	
	/**
	 * Default constructor
	 * @param	suit
	 * @param	symbol
	 */
	public Card (Suit suit, Symbol symbol) {
		this.symbol = symbol;
		this.suit = suit;
	}

	/**********************************************************************
	 * SYMBOL
	 **********************************************************************/

	private final Symbol symbol;

	/**
	 * Inspect the symbol of the card.
	 */
	public Symbol getSymbol() {
		return symbol;
	}
	
	/**********************************************************************
	 * SUIT
	 **********************************************************************/

	private final Suit suit;
	
	/**
	 * Inspect the suit of the card.
	 */
	public Suit getSuit() {
		return suit;
	}
	
	/**********************************************************************
	 * MISC & COMPARISON
	 **********************************************************************/
	
	/**
	 * Returns the points value of the card, which is the value of the symbol.
	 * @return	getSymbol.getValue();
	 */
	public int getValue() {
		return getSymbol().getValue();
	}
	
	/**
	 * Compares to another card. If the calling Card is 'stronger' than the given card,
	 * the result is a positive integer, otherwise negative. If they're equally 'strong',
	 * 0 is returned. The card strength corresponds to the strength of the symbol.
	 * @param other	The other card.
	 */
	public int compareTo(Card other) {
		if ( other == null ) {
			return 1;
		} else {
			return getSymbol().getStrength() - other.getSymbol().getStrength();
		}
	}
	
	/**
	 * Checks whether this card is stronger than the given card.
	 * @param	other
	 * 			The other card.
	 * @return	true if this card is stronger.
	 */
	public boolean isStrongerThan(Card other) {
		return this.getSymbol().isStrongerThan(other.getSymbol());
	}

	/**
	 * Checks whether this card has the same suit and symbol as the given card.
	 * @param	other
	 * 			The other card.
	 * @return	True if Suit and Symbol are equal
	 * 			| (getSymbol() == other.getSymbol()) && (getSuit() == other.getSuit())
	 */
	public boolean equals(Card other) {
		return (getSymbol() == other.getSymbol()) && (getSuit() == other.getSuit());
	}
	
	public String toString() {
		return symbol.toString() + " OF " + suit.toString() + "S";
	}
	
	/**
	 * To work with Set:
	 */
	
	public boolean equals(Object other) {
		if (other instanceof Card) {
			return equals((Card) other);
		} else {
			return false;
		}
	}
	
	public int hashCode() {
		return 100*(getSuit().ordinal()) + getSymbol().getStrength();
	}
	
}
<file_sep>package exception;

import core.Card;

public class InvalidCardException extends Exception {
	
	private static final long serialVersionUID = -3872951161842873464L;
	private Card card;

	public InvalidCardException(Card card) {
		this.card = card;
	}

	public String toString() {
		return "Invalid card at this time: " + card.toString() + "\n" + super.toString();
	}
}
<file_sep>package player;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import core.Card;

/**
 * Represents a pool of cards. These are all the cards a team has won
 * during a round.
 * 
 * @author <NAME>
 */
public class Pool implements Iterable<Card> {
	
	/**
	 * Initializes a new pool
	 */
	public Pool() {
		reset();
	}
	
	/**********************************************************************
	 * CARDS
	 **********************************************************************/
	
	private List<Card> cardList;
	
	public int getSize() {
		return cardList.size();
	}
	
	public void push(Card card) {
		cardList.add(card);
	}
	
	public void pushAll(List<Card> cards) {
		for ( Card card : cards ) {
			push(card);
		}
	}
	
	public void reset() {
		cardList = new ArrayList<Card>();
	}
	
	public boolean isEmpty() {
		return cardList.isEmpty();
	}
	
	/**********************************************************************
	 * SCORE
	 **********************************************************************/
	
	public int getScore() {
		int score = 0;
		for ( Card card : cardList ) {
			score += card.getValue();
		}
		return score;
	}
	
	/**********************************************************************
	 * ITERATE
	 **********************************************************************/

	@Override
	public Iterator<Card> iterator() {
		return cardList.iterator();
	}

}
<file_sep>package AI.daan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import player.InformationHandle;
import AI.Intelligence;
import AI.daan.Deducer.Possibility;
import core.Card;
import core.ShuffleCommand;
import core.Suit;

public class MonteCarloAI implements Intelligence {
	
	private Random rand;
	private Deducer deducer;
	
	private static int N = 100; // number of simulations per card
	private static int M = 10; // number of steps for each simulation
	
	private int trickNumber; // temp var to maintain current trick number, functionality should be present in the handle instead
	
	public MonteCarloAI() {
		this.rand = new Random();
	}

	/**
	 * Will run a number of simulations for each valid card and select
	 * the one with the best average results.
	 * Each simulation is based on a (pseudo-)random starting state
	 * which satisfies all constraints that have been deduced so far.
	 */
	@Override
	public Card chooseCard(List<Card> hand, InformationHandle info) {
		List<Card> valid = new ArrayList<Card>();
		for ( Card card : hand ) {
			if ( info.isValidCard(card) ) {
				valid.add(card);
			}
		}
		if ( valid.isEmpty() ) {
			throw new RuntimeException("unexpected");
		}
		
		List<Possibility> ps = new ArrayList<Possibility>();
		for ( int j = 0 ; j < N ; j++ ) {
			ps.add(deducer.generatePossibility());
		}
		
		TreeMap<Integer,Card> scores = new TreeMap<Integer,Card>();
		for ( Card option : valid ) {
			List<Card> optHand = new ArrayList<Card>(hand);
			optHand.remove(option);
			List<Card> optField = new ArrayList<Card>(info.inspectField());
			optField.add(option);
			
			int sum = 0;
			for ( Possibility p : ps ) {
				Simulation s = new Simulation(info.inspectTrump(),optField,
						optHand,p.enemy1,p.ally,p.enemy2);
				
				/*System.out.println("----");
				for (Card c : s.getMe()) {
					System.out.println(c);
				}
				System.out.println("----");
				for (Card c : s.getEnemy1()) {
					System.out.println(c);
				}
				System.out.println("----");
				for (Card c : s.getAlly()) {
					System.out.println(c);
				}
				System.out.println("----");
				for (Card c : s.getEnemy2()) {
					System.out.println(c);
				}
				System.out.println("----");*/
				
				sum += s.run(Math.min(M, 9-trickNumber));
				
			}
			scores.put(sum/N,option);
			// throw new RuntimeException("intended stop");
		}
		
		return scores.lastEntry().getValue();	
	}

	@Override
	public void notify(List<Card> hand, InformationHandle info) {		
		
		List<Card> field = info.inspectField();
		List<Card> reducedField = field.subList(0, field.size()-1);
		
		int turn = reducedField.size();
		Card p = field.get(turn);
		
		int myTurn = info.getTurn();
		
		if (turn != myTurn) {
			deducer.feedAction((turn-myTurn+3) % 4, p, reducedField, info.inspectTrump());
		}
		
		//System.out.println(deducer.getLog());
		
		// update of the trick number
		if ( field.size() == 4) {
			trickNumber++;
			//System.out.println("Trick is now: " + trickNumber);
		}
		
	}
	
	
	/**
	 * Project 2501 trump algo
	 */
	private final int CORRECTION = 3;
	@Override
	public Suit chooseTrump(List<Card> hand) {
		Map<Suit,Double> amounts = new HashMap<Suit,Double>();
		for ( Suit s : Suit.values()) {
			amounts.put(s, (double) 0);
		}
		for ( Card c : hand ) {
			double x = amounts.get(c.getSuit()) + c.getSymbol().getStrength() + CORRECTION;
			amounts.put(c.getSuit(), x);
		}
		Suit best = Suit.HEART;
		for ( Suit s : amounts.keySet() ) {
			if ( amounts.get(s) > amounts.get(best)) { best = s; }
		}
		return best;
	}

	/**
	 * Returns the name of the AI.
	 */
	@Override
	public String identify() {
		return "[Monte Carlo]";
	}
	
	private final int KNOCK_TRESHOLD = 10;

	@Override
	public boolean chooseToKnock(List<Card> hand, Suit trump) {
		int power = 0;
		for ( Card card : hand ) {
			if ( card.getSuit().equals(trump) ) {
				power += card.getSymbol().getStrength();
			}
		}
		return (power > KNOCK_TRESHOLD);
	}
	
	@Override
	public List<ShuffleCommand> chooseShuffleCommands() {
		return Arrays.asList(ShuffleCommand.createRandomShuffleCommand());
	}

	@Override
	public void notifyOfHand(List<Card> hand) {
		this.trickNumber = 1;
		deducer = new Deducer();
		deducer.feedHand(hand);
	}
	
}
<file_sep>package gui;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

import core.Card;
import core.Suit;

public interface UIController {

	public abstract void initialize() throws InvocationTargetException, InterruptedException;

	/**********************************************************************
	 * VISUAL UPDATES
	 **********************************************************************/

	public abstract void updateHand(List<Card> hand, List<Card> valid);

	public abstract void updateField(List<Card> field, int turn);

	public abstract void updateTrump(Suit trump);

	public abstract void informOfInvalidCardChoice();
	
	public abstract void updateTrick(Card[] trick);
	
	public abstract void updateMultiplier(int multiplier);
	
	public abstract void informOfRoundScore(int ally, int enemy, int allyTotal, int enemyTotal);
	
	public abstract void updatePoolScores(int ally, int enemy);
	
	public abstract void updateTotalScores(int ally, int enemy);
	
	/**********************************************************************
	 * CHOICES
	 **********************************************************************/
	
	public abstract Object setupCardChoice();

	public abstract Card fetchChosenCard();
	
	public abstract boolean fetchKnockChoice(Suit trump);
	
	public abstract Suit fetchChosenTrump();

	/**********************************************************************
	 * NAMES & STRINGS
	 **********************************************************************/

	public abstract void setNames(String playerName, String other1, String other2, String other3);
	
	public abstract void setDealerName(String dealerName);

	public abstract void setFrameTitle(String title);
	
	/**********************************************************************
	 * PAUZE
	 **********************************************************************/
	
	public void pauze();


}<file_sep>package gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;

import core.Card;
import core.Suit;

public class GameFrame extends JFrame {

	private static final long serialVersionUID = -2612918951205849060L;
	
	private final int CARD_WIDTH = 71;
	private final int CARD_HEIGHT = 96;
	
	private final int BOX_SPACING = 2;
	private final int BORDER_WIDTH = 2;

	/**
	 * Create the frame.
	 */
	public GameFrame() {
		loadImages();
		handMap = new HashMap<>();
		
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 816, 548);
		setMinimumSize(new Dimension(816, 548));
		
		JPanel contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		setContentPane(contentPane);
		contentPane.setLayout(new BorderLayout(0, 0));
		
		JPanel center = new JPanel() {
			private static final long serialVersionUID = 1L;

			@Override
			protected void paintComponent(Graphics g)
			{
				super.paintComponent(g);
				g.drawImage(feltBack, 0, 0, getWidth(), getHeight(), null);
			}
		};
		center.setBackground(new Color(0, 128, 0));
		contentPane.add(center, BorderLayout.CENTER);
		center.setLayout(new GridBagLayout());
		
		JPanel east = new JPanel();
		contentPane.add(east, BorderLayout.EAST);
		east.setLayout(new GridBagLayout());
		
		//----------------------------------------
		
		JPanel left = new JPanel();
		left.setOpaque(false);
		left.setBounds(0, 26, 656, 484);
		left.setPreferredSize(new Dimension(656,484));
		center.add(left);
		left.setLayout(null);
		
		//----------------------------------------
		
		JPanel right = new JPanel();
		right.setPreferredSize(new Dimension(144,484));
		GridBagConstraints gbc_right = new GridBagConstraints();
		gbc_right.weighty = 1.0;
		gbc_right.anchor = GridBagConstraints.NORTH;
		east.add(right, gbc_right);
		right.setLayout(null);
		
		/**********************************************************************
		 * MENU BAR
		 **********************************************************************/
		
		JMenuBar menuBar = new JMenuBar();
		menuBar.setPreferredSize(new Dimension(800,26));
		contentPane.add(menuBar, BorderLayout.NORTH);
		
		JMenu settingsMenu = new JMenu("Settings");
		menuBar.add(settingsMenu);
		
		JCheckBoxMenuItem chckbxFullscreen = new JCheckBoxMenuItem("FullScreen");
		settingsMenu.add(chckbxFullscreen);
		chckbxFullscreen.addItemListener(new ItemListener() {

			@Override
			public void itemStateChanged(ItemEvent e) {
				if ( e.getStateChange() == ItemEvent.SELECTED ) {
					(GameFrame.this).setExtendedState(Frame.MAXIMIZED_BOTH); 
				} else {
					(GameFrame.this).setBounds(100, 100, 816, 548);
				}
			}
			
		});
		
		/**********************************************************************
		 * FIELD PANEL
		 **********************************************************************/
		
		JPanel fieldPanel = new JPanel();
		fieldPanel.setOpaque(false);
		fieldPanel.setBounds(182, 61, 295, 230);
		fieldPanel.setLayout(null);
		left.add(fieldPanel);
		
		EmptyBorder padding = new EmptyBorder(BOX_SPACING,BOX_SPACING,BOX_SPACING,BOX_SPACING);
		topField = Box.createHorizontalBox();
		topField.setBounds(109, 10, CARD_WIDTH + (BOX_SPACING+BORDER_WIDTH)*2, CARD_HEIGHT + (BOX_SPACING+BORDER_WIDTH)*2);
		topField.setBorder(BorderFactory.createCompoundBorder(new LineBorder(new Color(0, 96, 32), BORDER_WIDTH), padding));
		
		leftField = Box.createHorizontalBox();
		leftField.setBounds(24, 68, CARD_WIDTH + (BOX_SPACING+BORDER_WIDTH)*2, CARD_HEIGHT + (BOX_SPACING+BORDER_WIDTH)*2);
		leftField.setBorder(BorderFactory.createCompoundBorder(new LineBorder(new Color(0, 96, 32), BORDER_WIDTH), padding));
		
		rightField = Box.createHorizontalBox();
		rightField.setBounds(194, 68, CARD_WIDTH + (BOX_SPACING+BORDER_WIDTH)*2, CARD_HEIGHT + (BOX_SPACING+BORDER_WIDTH)*2);
		rightField.setBorder(BorderFactory.createCompoundBorder(new LineBorder(new Color(0, 96, 32), BORDER_WIDTH), padding));
		
		bottomField = Box.createHorizontalBox();
		bottomField.setBounds(109, 120, CARD_WIDTH + (BOX_SPACING+BORDER_WIDTH)*2, CARD_HEIGHT + (BOX_SPACING+BORDER_WIDTH)*2);
		bottomField.setBorder(BorderFactory.createCompoundBorder(new LineBorder(new Color(0, 96, 32), BORDER_WIDTH), padding));
		
		fieldPanel.add(leftField);
		fieldPanel.add(rightField);
		fieldPanel.add(topField);
		fieldPanel.add(bottomField);
		
		/**********************************************************************
		 * PLAYER NAMES
		 **********************************************************************/
		
		rightNameLabel = new JLabel("Freddy", SwingConstants.CENTER);
		rightNameLabel.setForeground(Color.WHITE);
		rightNameLabel.setFont(new Font("Tahoma", Font.BOLD, 20));
		rightNameLabel.setBounds(479, 157, 180, 27);
		left.add(rightNameLabel);
		
		topNameLabel = new JLabel("Freddy", SwingConstants.CENTER);
		topNameLabel.setForeground(Color.WHITE);
		topNameLabel.setFont(new Font("Tahoma", Font.BOLD, 20));
		topNameLabel.setBounds(240, 23, 180, 27);
		left.add(topNameLabel);
		
		bottomNameLabel = new JLabel("Freddy", SwingConstants.CENTER);
		bottomNameLabel.setForeground(Color.WHITE);
		bottomNameLabel.setFont(new Font("Tahoma", Font.BOLD, 20));
		bottomNameLabel.setBounds(240, 302, 180, 27);
		left.add(bottomNameLabel);
		
		leftNameLabel = new JLabel("Freddy", JLabel.CENTER);
		leftNameLabel.setFont(new Font("Tahoma", Font.BOLD, 20));
		leftNameLabel.setForeground(Color.WHITE);
		leftNameLabel.setBounds(0, 157, 180, 27);
		left.add(leftNameLabel);
		
		/**********************************************************************
		 * HAND PANEL
		 **********************************************************************/
		
		handPanel = new JPanel();
		handPanel.setOpaque(false);
		handPanel.setBounds(8, 365, 639, 108);
		handPanel.setLayout(null);
		left.add(handPanel);
		
		/**********************************************************************
		 * ...
		 **********************************************************************/
		
		JLabel lblTrump = new JLabel("Trump:",JLabel.CENTER);
		lblTrump.setForeground(Color.DARK_GRAY);
		lblTrump.setBounds(9, 11, 125, 17);
		lblTrump.setFont(new Font("Tahoma", Font.PLAIN, 14));
		right.add(lblTrump);
		
		trumpPanel = new JPanel();
		trumpPanel.setBorder(new LineBorder(new Color(128, 128, 128)));
		trumpPanel.setLayout(new GridBagLayout());
		trumpPanel.setBounds(9, 30, 125, 88);
		right.add(trumpPanel);
		
		btnPreviousTrick = new JButton("<html><center>Previous Trick</center></html>");
		btnPreviousTrick.setFont(new Font("Tahoma", Font.PLAIN, 14));
		btnPreviousTrick.setBounds(9, 388, 125, 37);
		btnPreviousTrick.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				displayPreviousTrick();				
			}
		});
		btnPreviousTrick.setEnabled(false);
		
		right.add(btnPreviousTrick);
		
		JLabel lblMultText = new JLabel("Multiplier:",JLabel.CENTER);
		lblMultText.setForeground(Color.DARK_GRAY);
		lblMultText.setFont(new Font("Tahoma", Font.PLAIN, 14));
		lblMultText.setBounds(9, 158, 125, 17);
		right.add(lblMultText);
		
		JPanel multPanel = new JPanel();
		multPanel.setForeground(Color.GRAY);
		multPanel.setBorder(new LineBorder(Color.GRAY));
		multPanel.setBounds(9, 177, 125, 37);
		right.add(multPanel);
		multPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
		
		lblMultNumber = new JLabel("x1");
		lblMultNumber.setForeground(Color.GRAY);
		lblMultNumber.setFont(new Font("Tahoma", Font.BOLD, 20));
		multPanel.add(lblMultNumber);
		
		dealerPanel = new JPanel();
		dealerPanel.setForeground(Color.GRAY);
		dealerPanel.setBorder(new LineBorder(new Color(128, 128, 128)));
		dealerPanel.setBounds(9, 115, 125, 32);
		right.add(dealerPanel);
		dealerPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
		
		lblDealer = new JLabel("Freddy");
		lblDealer.setForeground(Color.GRAY);
		lblDealer.setFont(new Font("Tahoma", Font.BOLD, 18));
		dealerPanel.add(lblDealer);
		
		JButton undoButton = new JButton("<html><center>Undo</center></html>");
		undoButton.setFont(new Font("Tahoma", Font.PLAIN, 14));
		undoButton.setBounds(9, 436, 125, 37);
		undoButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent ae) {
				JOptionPane.showMessageDialog(GameFrame.this,
						"Sorry, the table is sticky.",
						"Undo",
						JOptionPane.INFORMATION_MESSAGE);
			}
			
		});
		right.add(undoButton);
		
		JLabel lblPoolScores = new JLabel("Pool scores:", SwingConstants.CENTER);
		lblPoolScores.setForeground(Color.DARK_GRAY);
		lblPoolScores.setFont(new Font("Tahoma", Font.PLAIN, 14));
		lblPoolScores.setBounds(9, 225, 125, 17);
		right.add(lblPoolScores);
		
		JPanel poolScorePanelAlly = new JPanel();
		poolScorePanelAlly.setBorder(new TitledBorder(new LineBorder(new Color(128, 128, 128)), "Ally", TitledBorder.CENTER, TitledBorder.BOTTOM, null, Color.DARK_GRAY));
		poolScorePanelAlly.setBounds(9, 244, 61, 50);
		right.add(poolScorePanelAlly);
		poolScorePanelAlly.setLayout(new CardLayout(0, 0));
		
		JPanel poolScorePanelEnemy = new JPanel();
		poolScorePanelEnemy.setBorder(new TitledBorder(new LineBorder(new Color(128, 128, 128)), "Enemy", TitledBorder.CENTER, TitledBorder.BOTTOM, null, Color.DARK_GRAY));
		poolScorePanelEnemy.setBounds(73, 244, 61, 50);
		right.add(poolScorePanelEnemy);
		poolScorePanelEnemy.setLayout(new CardLayout(0, 0));
		
		lblEnemyPoolScore = new JLabel("0",JLabel.CENTER);
		lblEnemyPoolScore.setForeground(Color.GRAY);
		lblEnemyPoolScore.setFont(new Font("Tahoma", Font.BOLD, 18));
		poolScorePanelEnemy.add(lblEnemyPoolScore, "name_28394980645032");
		
		lblAllyPoolScore = new JLabel("0",JLabel.CENTER);
		lblAllyPoolScore.setForeground(Color.GRAY);
		lblAllyPoolScore.setFont(new Font("Tahoma", Font.BOLD, 18));
		poolScorePanelAlly.add(lblAllyPoolScore, "name_28357213157586");
		
		JLabel lblTotalScores = new JLabel("Total scores:", SwingConstants.CENTER);
		lblTotalScores.setForeground(Color.DARK_GRAY);
		lblTotalScores.setFont(new Font("Tahoma", Font.PLAIN, 14));
		lblTotalScores.setBounds(9, 299, 125, 17);
		right.add(lblTotalScores);
		
		JPanel totalScorePanelAlly = new JPanel();
		totalScorePanelAlly.setBorder(new TitledBorder(new LineBorder(new Color(128, 128, 128)), "Ally", TitledBorder.CENTER, TitledBorder.BOTTOM, null, Color.DARK_GRAY));
		totalScorePanelAlly.setBounds(9, 317, 61, 50);
		right.add(totalScorePanelAlly);
		totalScorePanelAlly.setLayout(new CardLayout(0, 0));
		
		JPanel totalScorePanelEnemy = new JPanel();
		totalScorePanelEnemy.setBorder(new TitledBorder(new LineBorder(new Color(128, 128, 128)), "Enemy", TitledBorder.CENTER, TitledBorder.BOTTOM, null, Color.DARK_GRAY));
		totalScorePanelEnemy.setBounds(73, 317, 61, 50);
		right.add(totalScorePanelEnemy);
		totalScorePanelEnemy.setLayout(new CardLayout(0, 0));
		
		lblAllyTotalScore = new JLabel("0", SwingConstants.CENTER);
		lblAllyTotalScore.setForeground(Color.GRAY);
		lblAllyTotalScore.setFont(new Font("Tahoma", Font.BOLD, 18));
		totalScorePanelAlly.add(lblAllyTotalScore, "name_29314576817384");
		
		lblEnemyTotalScore = new JLabel("0", SwingConstants.CENTER);
		lblEnemyTotalScore.setForeground(Color.GRAY);
		lblEnemyTotalScore.setFont(new Font("Tahoma", Font.BOLD, 18));
		totalScorePanelEnemy.add(lblEnemyTotalScore, "name_29401204335232");
	}
	
	/**********************************************************************
	 * LOCK
	 **********************************************************************/
	
	private Object lock;
	
	public void attach(Object lock) {
		this.lock = lock;
	}
	
	/**********************************************************************
	 * HAND
	 **********************************************************************/
	
	private JPanel handPanel;
	
	private Map<JLabel,Card> handMap;
	private JLabel selected;
	private boolean handActive = false;
	private Object handActiveLock = new Object();
	
	private static final int HAND_PADDING = 4;
	private static final int HAND_X_OFFSET = 18;
	private static final int HAND_Y_OFFSET = 4;
	private static final int SELECTION_THICKNESS = 2;
	private static final Color SELECTION_VALID = new Color(50, 255, 50);
	private static final Color SELECTION_INVALID = new Color(255, 50, 50);
	
	public void activateHand() {
		synchronized(handActiveLock) {
			this.handActive = true;
		}
	}
	
	public void deactivateHand() {
		synchronized(handActiveLock) {
			this.handActive = false;
		}
	}
	
	public void updateHand(List<Card> hand, final List<Card> valid) {
		Collections.sort(hand, new Comparator<Card>() {
			@Override
			public int compare(Card c1, Card c2) {
				if (c1.getSuit().equals(c2.getSuit())) {
					return c1.compareTo(c2);
				} else {
					return c1.getSuit().compareTo(c2.getSuit());
				}
			}
		});
		clearSelectedCard();
		handMap.clear();
		handPanel.removeAll();
		int cardNumber = 0;
		int x_offset = HAND_X_OFFSET + (CARD_WIDTH + HAND_PADDING)*(8-hand.size())/2;
		for ( Card card : hand ) {
			JLabel cardLabel = new JLabel(getCardImage(card),JLabel.CENTER);
			int width = CARD_WIDTH + SELECTION_THICKNESS*2;
			int height = CARD_HEIGHT + SELECTION_THICKNESS*2;
			cardLabel.setBounds(x_offset + (CARD_WIDTH + HAND_PADDING)*cardNumber, HAND_Y_OFFSET, width, height);
			cardLabel.addMouseListener(new MouseListener() {
				@Override
				public void mouseEntered(MouseEvent e) {
					synchronized(handActiveLock) {
						if ( handActive ) {
							selected = ((JLabel) e.getSource());
							unborderAllHandCards();
							Color selectionColor = (valid.contains(handMap.get(selected))) ? SELECTION_VALID : SELECTION_INVALID;
							selected.setBorder(BorderFactory.createLineBorder(selectionColor,SELECTION_THICKNESS));
						}
					}
				}

				@Override
				public void mouseExited(MouseEvent e) {
					synchronized(handActiveLock) {
						if ( handActive ) { 
							unborderAllHandCards();
						}
					}
				}

				@Override
				public void mousePressed(MouseEvent e) {}

				@Override
				public void mouseReleased(MouseEvent e) {
					synchronized(handActiveLock) {
						if ( handActive && e.getButton() == MouseEvent.BUTTON1) {
							submitCard();
						}
					}
				}

				@Override
				public void mouseClicked(MouseEvent e) {}
			});
			handPanel.add(cardLabel);
			handMap.put(cardLabel, card);
			cardNumber++;
		}
		handPanel.revalidate();
		handPanel.repaint();
	}
	
	private void unborderAllHandCards() {
		for ( JLabel cl : handMap.keySet() ) {
			cl.setBorder(BorderFactory.createEmptyBorder());
		}
	}
	
	private void submitCard() {
		synchronized(lock) {
			if ( lock != null ) {
				lock.notify();
			}
		}
	}
	
	public Card getSelectedCard() {
		return handMap.get(selected);
	}
	
	private void clearSelectedCard() {
		this.selected = null;
	}
	
	/**********************************************************************
	 * FIELD
	 **********************************************************************/
	
	private Box topField;
	private Box bottomField;
	private Box leftField;
	private Box rightField;

	public void updateField(List<Card> field, int offset) {
		Box[] fieldBoxes = { topField, rightField, bottomField, leftField };
		topField.removeAll();
		bottomField.removeAll();
		leftField.removeAll();
		rightField.removeAll();
		for ( int i = 0 ; i < Math.min(field.size(),4) ; i++ ) {
			fieldBoxes[(i-offset+2+4) % 4].add(new JLabel(getCardImage(field.get(i))));
		}
		for ( Box b : fieldBoxes ) {
			b.revalidate();
			b.repaint();
		}
	}
	
	/**********************************************************************
	 * TRUMP, DEALER & MULTIPLIER
	 **********************************************************************/
	
	private JPanel trumpPanel;
	private JLabel lblMultNumber;
	private JPanel dealerPanel;
	private JLabel lblDealer;
	
	public void updateTrump(Suit trump) {
		trumpPanel.removeAll();
		if ( trump != null ) {
			trumpPanel.add(new JLabel(getSuitImage(trump),JLabel.CENTER));
		} else {
			JLabel x = new JLabel("X",JLabel.CENTER);
			x.setFont(new Font("Tahoma",Font.BOLD,40));
			x.setForeground(Color.GRAY);
			trumpPanel.add(x);
		}
		trumpPanel.revalidate();
		trumpPanel.repaint();
	}
	
	public void clearTrump() {
		trumpPanel.removeAll();
		trumpPanel.revalidate();
		trumpPanel.repaint();
	}
	
	public void updateDealerName(String dealerName) {
		lblDealer.setText(dealerName);
		lblDealer.revalidate();
	}
	
	public void updateMultiplier(int multiplier) {
		lblMultNumber.setText("x" + multiplier);
		lblDealer.revalidate();
	}
	
	/**********************************************************************
	 * SCORES
	 **********************************************************************/
	
	private JLabel lblEnemyPoolScore;
	private JLabel lblAllyPoolScore;
	private JLabel lblEnemyTotalScore;
	private JLabel lblAllyTotalScore;
	
	public void updatePoolScores(int ally, int enemy) {
		lblAllyPoolScore.setText(Integer.toString(ally));
		lblAllyPoolScore.setForeground(ally > 30 ? new Color(50, 205, 50) : Color.GRAY);
		lblEnemyPoolScore.setText(Integer.toString(enemy));
		lblEnemyPoolScore.setForeground(enemy > 30 ? Color.RED: Color.GRAY);
		lblAllyPoolScore.revalidate();
		lblEnemyPoolScore.revalidate();
	}
	
	public void updateTotalScores(int ally, int enemy) {
		lblEnemyTotalScore.setText(Integer.toString(enemy));
		lblAllyTotalScore.setText(Integer.toString(ally));
		lblEnemyTotalScore.revalidate();
		lblAllyTotalScore.revalidate();
	}
	
	/**********************************************************************
	 * PREVIOUS TRICK
	 **********************************************************************/
	
	private JPanel trickCache = new JPanel();
	private JButton btnPreviousTrick;
	
	public void updatePreviousTrick(Card[] trick) {
		synchronized(trickCache) {
			trickCache.removeAll();
			for ( Card card : trick ) {
				trickCache.add(new JLabel(getCardImage(card)));
			}
			trickCache.revalidate();
		}
		btnPreviousTrick.setEnabled(true);
	}
	
	public void displayPreviousTrick() {
		synchronized(trickCache) {
			JOptionPane.showMessageDialog(this,trickCache,"Previous Trick",JOptionPane.PLAIN_MESSAGE);
		}		
	}
	
	/**********************************************************************
	 * PLAYER LABELS
	 **********************************************************************/
	
	private JLabel rightNameLabel;
	private JLabel topNameLabel;
	private JLabel bottomNameLabel;
	private JLabel leftNameLabel;
	
	public void updateNames(String guiPlayerName, String leftName, String topName, String rightName) {
		this.rightNameLabel.setText(rightName);
		this.leftNameLabel.setText(leftName);
		this.topNameLabel.setText(topName);
		this.bottomNameLabel.setText(guiPlayerName);
	}
	
	/**********************************************************************
	 * IMAGES
	 **********************************************************************/
	
	private BufferedImage allCards;
	private BufferedImage allSuits;
	private BufferedImage feltBack;
	
	private void loadImages() {
		ClassLoader cl = this.getClass().getClassLoader();
		try {
			allCards = ImageIO.read(cl.getResource("resource/allCards.jpg"));
			allSuits = ImageIO.read(cl.getResource("resource/allSuits100.png"));
			feltBack = ImageIO.read(cl.getResource("resource/felt.png"));
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
	private ImageIcon getCardImage(Card card) {
		int suit = 0;
		switch(card.getSuit()) {
			case CLUB: suit = 0; break;
			case SPADE: suit = 1; break;
			case HEART: suit = 2; break;
			case DIAMOND: suit = 3; break;
		}
		int x = 1 + (CARD_WIDTH + 2)*card.getSymbol().getStrength();
		int y = 1 + (CARD_HEIGHT + 2)*suit;
		return new ImageIcon(allCards.getSubimage(x, y, CARD_WIDTH, CARD_HEIGHT));
	}
	
	private static final int SUIT_SIZE = 50;
	
	private ImageIcon getSuitImage(Suit suit) {
		switch(suit) {
		case SPADE: return new ImageIcon(allSuits.getSubimage(0, 0, SUIT_SIZE, SUIT_SIZE));
		case HEART: return new ImageIcon(allSuits.getSubimage(SUIT_SIZE, 0, SUIT_SIZE, SUIT_SIZE));
		case DIAMOND: return new ImageIcon(allSuits.getSubimage(0, SUIT_SIZE, SUIT_SIZE, SUIT_SIZE));
		case CLUB: return new ImageIcon(allSuits.getSubimage(SUIT_SIZE, SUIT_SIZE, SUIT_SIZE, SUIT_SIZE));
		}
		return null;
	}
}
<file_sep>package core;

import java.util.ArrayList;
import java.util.List;

import player.InformationHandle;
import player.Player;
import player.Team;
import exception.IllegalShuffleException;
import exception.InvalidCardException;


/**
 * Sequence of 8 tricks. This class is responsible for
 * all game-controlling logic that occurs within
 * a single game.
 * @author Daan
 */
public class Round {
	
	Team team1;
	Team team2;
	Player dealer;
	Suit trump;
	List<Card> field;
	Player[] sequence;
	Card[] previousTrick;
	int multiplier;
	Deck deck;
	private static final int MIN_COMMAND_POINTS = 6;
	
	public Round(Team team1, Team team2, Player dealer, Deck deck, int multiplier) {
		this.team1 = team1;
		this.team2 = team2;
		this.dealer = dealer;
		this.field = new ArrayList<Card>();
		this.sequence = new Player[4];
		this.multiplier = multiplier;
		this.previousTrick = null;
		this.deck = deck;
	}
	
	/**********************************************************************
	 * MULTIPLIER
	 **********************************************************************/
	
	public int getMultiplier() {
		return multiplier;
	}
	
	/**********************************************************************
	 * TRUMP
	 **********************************************************************/
	
	/**
	 * Returns the trump suit when one was declared.
	 * Returns null if:<br/>
	 * 1) no trump suit was declared yet.
	 * 2) the round is no-trump
	 * @return a Suit, or null
	 */
	public Suit getTrump() {
		return trump;
	}
	
	
	/**********************************************************************
	 * RUN
	 **********************************************************************/
	
	/**
	 * Executes a sequence of 8 tricks and returns the winning team
	 * @throws IllegalShuffleException 
	 * @throws InvalidCardException 
	 */
	public void run() throws IllegalShuffleException, InvalidCardException {
		// STEP 1: BUILD PLAYER SEQUENCE
		Player starter = getSuccessor(dealer);
		buildSequence(starter);
		
		// STEP 1.2: EXAMINE SEQUENCE
		Logger.log("------- PLAYER ORDER -------");
		for ( Player player : sequence ) {
			Logger.log(player);
		}
		
		// STEP 2: SHUFFLE & DEAL CARDS
		List<ShuffleCommand> commands = dealer.chooseShuffleCommands();
		if ( legalShuffleCommands(commands)) {
			deck.execute(commands);
		} else {
			throw new IllegalShuffleException();
		}
		deal323(deck);
		for ( Player player : sequence ) {
			player.notifyOfNewRound(dealer.getName());
		}
		
		// STEP 2.2: EXAMINE HANDS
		Logger.log("====== " + dealer + " deals: ======");
		for ( Player player : sequence) {
			Logger.log(player + " got the following: ");
			for ( Card c : player.getHand() ) {
				Logger.log(c);
			}
			Logger.log("---------------------------- ");
		}
		
		// STEP 3: DEFINE TRUMP & KNOCKING
		trump = dealer.chooseTrump();
		if (trump == null) {
			multiplier *= 2;
		}
		if (starter.knocks(trump)) {
			Logger.log(starter.getName() + " knocked");
			multiplier *= 2;
		} else if ( getTeamPlayer(starter).knocks(trump) ) {
			Logger.log(getTeam(starter) + " knocked.");
			multiplier *= 2;
		}
		for ( Player player : sequence ) {
			player.notifyOfMultiplier(trump, multiplier);
		}
		
		// STEP 3.2: EXAMINE THE TRUMP
		Logger.log(dealer + " chose the trump: " + trump);
		Logger.log("---------------------------- ");
		
		// STEP 4: TRICKS
		for (int trick = 1 ; trick <= 8 ; trick++) {
			// STEP 4.1: EACH PLAYER PUTS A CARD 
			for ( Player player : sequence ) {
				InformationHandle info = new InformationHandle(player, this);
				Card card = player.chooseCard(info);
				if (isValidCard(card, player)) {
					field.add(card);
					player.getHand().removeCard(card);
					Logger.log(player + " plays " + card);
					for ( Player peer : sequence ) {
						peer.notify(new InformationHandle(peer,this));
					}
				} else {
					throw new InvalidCardException(card);
				}
			}
			// STEP 4.2: DETERMINE WINNER
			Player winner = getOwner(getBestCard());
			Logger.log("-- Trick (" + trick + ") winner is " + winner + " --");
			Team winningTeam = getTeam(winner);
			
			// STEP 4.3: CARDS ARE AWARDED TO WINNING TEAMS POOL
			winningTeam.getPool().pushAll(field);
			
			// STEP 4.4: STORE THE TRICK AS PREVIOUS TRICK, NOTIFY PLAYERS & CLEAR FIELD
			storeTrick();
			
			// STEP 4.5: REBUILD PLAYER SEQUENCE
			buildSequence(winner);
		}
	}
	
	private boolean legalShuffleCommands(List<ShuffleCommand> commands) {
		int points = 0;
		for ( ShuffleCommand command : commands ) {
			points += command.getPoints();
		}
		return (points >= MIN_COMMAND_POINTS);
	}

	private void storeTrick() {
		previousTrick = new Card[4];
		for ( int i = 0 ; i < 4 ; i++ ) {
			previousTrick[i] = field.get(i);
		}
		field.clear();
		for ( Player p : sequence ) {
			p.notifyOfTrick(previousTrick, new InformationHandle(p,this));
		}
	}

	/**********************************************************************
	 * CARD PLAYING LOGIC
	 **********************************************************************/
	
	/**
	 * Checks if the given card is valid to play at this time
	 * by the given player.
	 * This check can still be used if <i>this.trum == null</i>
	 * @param card
	 * @param player
	 * @return true if it is
	 */
	public boolean isValidCard(Card card, Player player) {
		if ( field.isEmpty() ) {
			// CASE 1: the field is empty: any card is allowed
			return true;
		} else {
			// CASE 2: the field is not empty
			Suit trickSuit = getTrickSuit();
			Team leadingTeam = getTeam(getOwner(getBestCard()));
			
			if ( leadingTeam.hasPlayer(player)) {
				// CASE 2.1: ally is leading
				if ( card.getSuit().equals(trickSuit)) {
					// CASE 2.1.1: follow: OK
					return true;
				} else {
					// CASE 2.1.2: don't follow
					if ( player.getHand().contains(trickSuit) ) {
						// CASE 2.1.2.1: didn't follow --> should have
						return false;
					} else {
						// CASE 2.1.2.2: couldn't follow: any card is OK
						return true;
					}
				}
			} else {
				// CASE 2.2: enemy is leading
				if ( card.getSuit().equals(trickSuit)) {
					// CASE 2.2.1: follow
					if ( getBestCard().getSuit().equals(trickSuit) ) {
						// CASE 2.2.1.1: none bought yet, must follow
						if ( player.getHand().contains(trickSuit) && player.getHand().getStrongestOfSuit(trickSuit).isStrongerThan(getBestCard())) {
							// CASE 2.2.1.1.1: can overpower
							if ( !card.isStrongerThan(getBestCard())) {
								// CASE 2.2.1.1.1.1: didn't overpower --> should have
								return false;
							} else {
								// CASE 2.2.1.1.1.2: did overpower: OK
								return true;
							}
						} else {
							// CASE 2.2.1.1.2: can't overpower: OK
							return true;
						}
					} else {
						// CASE 2.2.1.2: enemy bought, we have to follow, symbol doesn't matter: OK
						return true;
					}
				} else {
					// CASE 2.2.2: don't follow
					if ( player.getHand().contains(trickSuit)) {
						// CASE 2.2.2.1: didn't follow --> should have
						return false;
					} else {
						// CASE 2.2.2.2: couldn't follow
						if ( getBestCard().getSuit().equals(trump) ) {
							// CASE 2.2.2.2.1: enemy bought
							if ( player.getHand().contains(trump)) {
								// CASE 2.2.2.2.1.1: player has trump
								if ( player.getHand().getStrongestOfSuit(trump).isStrongerThan(getBestCard())) {
									// CASE 2.2.2.2.1.1.1: player has a better trump
									if ( card.getSuit().equals(trump) && card.isStrongerThan(getBestCard())) {
										// CASE 2.2.2.2.1.1.1.1: did overpower: OK
										return true;
									} else {
										// CASE 2.2.2.2.1.1.1.2: didn't overpower --> should have
										return false;
									}
								} else {
									// CASE 2.2.2.2.1.1.2: player has worse trump
									if ( card.getSuit().equals(trump)) {
										// CASE 2.2.2.2.1.1.2.1: player underbought
										if ( player.getHand().containsSuitOtherThan(trump)) {
											// CASE: couldn't do anything else
											return false;
										} else {
											// CASE: could do something else --> should have done that
											return true;
										}
										
									} else {
										// CASE 2.2.2.2.1.1.2.2: player didn't play trump: OK
										return true;
									}
								}
							} else {
								// CASE 2.2.2.2.1.2: player doesn't have trump: ok
								return true;
							}
							
						} else {
							// CASE 2.2.2.2.2: enemy did not buy, but owns highest card
							if ( player.getHand().contains(trump) && !card.getSuit().equals(trump)) {
								// CASE 2.2.2.2.2.1: player can buy, buy didn't --> should have
								return false;
							} else {
								// CASE 2.2.2.2.2.2: player couldn't buy or bought when he could: OK
								return true;
							}
						}
					}
				}				
			}
		}
	}

	/**********************************************************************
	 * SEQUENCE
	 **********************************************************************/
	
	private void buildSequence(Player starter) {
		sequence[0] = starter;
		for (int i = 1 ; i < 4 ; i++) {
			sequence[i] = getSuccessor(sequence[i-1]);
		}
	}

	private Player getSuccessor(Player player) {
		if ( player == team1.getPlayer1() ) {
			return team2.getPlayer1();
		} else if ( player == team1.getPlayer2() ) {
			return team2.getPlayer2();
		} else if ( player == team2.getPlayer1() ) {
			return team1.getPlayer2();
		} else {
			return team1.getPlayer1();
		}
	}
	
	/**
	 * Returns the index of the given player in the sequence
	 * @param player
	 * @return index
	 */
	public int getPositionInSequence(Player player) {
		for ( int i = 0 ; i < 4 ; i++ ) {
			if ( sequence[i] == player ) {
				return i;
			}
		}
		throw new RuntimeException("unexpected");
	}
	
	/**********************************************************************
	 * MISC
	 **********************************************************************/
	
	/**
	 * Returns the team of the given player (the ally team)
	 */
	public Team getTeam(Player player) {
		if ( team1.hasPlayer(player)) {
			return team1;
		} else {
			return team2;
		}
	}
	
	/**
	 * Returns the team opposing the given player (the enemy team)
	 */
	public Team getOpposingTeam(Player player) {
		if ( team1.hasPlayer(player)) {
			return team2;
		} else {
			return team1;
		}
	}
	
	/**
	 * Returns the team player of the given player
	 */
	private Player getTeamPlayer(Player player) {
		return getTeam(player).getTeamPlayerOf(player);
	}
	
	/**
	 * Returns the owner of the given card.
	 * (or null when the card isn't on the field)
	 */
	private Player getOwner(Card card) {
		return sequence[field.indexOf(card)];
	}
	
	/**
	 * Returns the best card on the field
	 */
	private Card getBestCard() {
		Card best = field.get(0);
		for ( Card card : field ) {
			if ((card.getSuit().equals(trump) && !best.getSuit().equals(trump))
				|| (best.getSuit().equals(card.getSuit()) && card.isStrongerThan(best))) {
				best = card;
			}
		}
		return best;
	}

	private void deal323(Deck deck) {
		for ( Player player : sequence ) {
			player.getHand().addCard(deck.draw());
			player.getHand().addCard(deck.draw());
			player.getHand().addCard(deck.draw());
		}
		for ( Player player : sequence ) {
			player.getHand().addCard(deck.draw());
			player.getHand().addCard(deck.draw());
		}
		for ( Player player : sequence ) {
			player.getHand().addCard(deck.draw());
			player.getHand().addCard(deck.draw());
			player.getHand().addCard(deck.draw());
		}
	}
	
	/**
	 * Return the suit of the first card in this trick.
	 * Returns null when the field is empty.
	 * @return a Suit, or null.
	 */
	public Suit getTrickSuit() {
		if ( !field.isEmpty() ) {
			return field.get(0).getSuit();
		} else {
			return null;
		}
	}

	public List<Card> getField() {
		return field;
	}
	
	/**
	 * Returns a clone of the previous trick if there is one,
	 * else returns null.
	 */
	public Card[] getPreviousTrickClone() {
		return previousTrick.clone();
	}
	

	public Player getDealer() {
		return this.dealer;
	}

	
}
