//***********************************************************
//*
//* File:           And1Player.java
//* Author:         Eric Li
//*
//*
//***********************************************************

package Cluedo.g2;

import Cluedo.*;
import ui.*;
import java.util.*;
import java.io.*;
import java.awt.Color;

import java.util.Random;

public class Group2PlayerEL implements IFCPlayer 
{

    //static final String _CNAME = "Giant Fat Player";
//    static final String _CNAME = "Give Us A+'s";
//    static final String _CNAME = "Got No Sleep";
    static final String _CNAME = "Giant Fat Player Returns";
    static final Color  _CCOLOR = new Color(0.0f, 1.0f, 0.0f);

    int N;  // total number of cards
    int k;  // number of cards per player
    int h;  // number of hidden cards
    int p;  // number of players
    int myIndex;

    int[] mycards;
    Cluedo _clue;

    // The actual player we use
    CombinedPlayer realPlayer;

    public void register(Cluedo __noclue) throws Exception 
    {

	    _clue = __noclue;
	    N = __noclue.numCards();
	    k = __noclue.numCardsPerPlayer();
	    h = __noclue.numHiddenCards();
	    p = __noclue.numPlayers();
	    myIndex = __noclue.indexOf(this);

        mycards = __noclue.getPlayerList(myIndex, this);

        realPlayer = new CombinedPlayer(this);
        realPlayer.register(__noclue);

    }

    public Move move() throws Exception 
    {
        Move RET = realPlayer.move();

        return RET;

    }

    public String name() throws Exception {
        return _CNAME;
    }

    public Color color() throws Exception {
        return _CCOLOR;
    }

    public boolean interactive() throws Exception {
        return false;
    }        

    public int question(int interrogator, int[] cardlist) throws Exception
    {
	    return realPlayer.question(interrogator, cardlist);
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        realPlayer.answer(interrogatee, mycardlist, response);
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        realPlayer.notifyPlayerExit(playerexited);
    }
    
    public int[] forcedGuess() throws Exception
    {
        return realPlayer.forcedGuess();
    }
}

// Things to consider:
//      How to represent info, what structures to use
//      How to update info  
//      Which player to ask each turn
//      How many cards to ask
//      When to guess   
//      How to answer when asked
//      

// Specific situations:
//      if A tells B it has 1 of 2 cards, and then A tells C it doesn't have 1 of those 2 cards
//          then it can be deduced that it has the other card
//     

class CorePlayer 
{
    Random _random;

    int N;  // total number of cards
    int k;  // number of cards per player
    int h;  // number of hidden cards
    int p;  // number of players
    int myIndex;

    Cluedo _clue;

    int[] mycards; // list of my cards

    IFCPlayer _this_; // handle to the instance of my player

    boolean[] ingame; // keeps track of players still in the game

    boolean[] hidden; // keeps track of which cards we definitely know aren't hidden

    int lastasked; // the player we last interrogated

    int lastturn; // this is the turn on which we last refreshed our info about opponents moves

    public CorePlayer(IFCPlayer handle)
    {
        _this_ = handle;
    }

    public void register(Cluedo __noclue) throws Exception 
    {
        _random = new Random();

	    _clue = __noclue;
	    N = __noclue.numCards();
	    k = __noclue.numCardsPerPlayer();
	    h = __noclue.numHiddenCards();
	    p = __noclue.numPlayers();
	    myIndex = __noclue.indexOf(_this_);

        mycards = __noclue.getPlayerList(myIndex, _this_);

        ingame = new boolean[p];
        for ( int i = 0; i < p; i++ )
        {
            ingame[i] = true;
        }

        hidden = new boolean[N];
        for ( int i = 0; i < N; i++ )
        {
            hidden[i] = true;
        }
        for ( int i = 0; i < mycards.length; i++ )
        {
            hidden[ mycards[i]-1 ] = false;
        }

        lastturn = 1;
    }

    public Move move() throws Exception 
    {
        Move RET = null;

        int[] hlist = getHidden();

        // Interrogate someone

        if ( hlist.length > h )
        {
            // ask players in sequence
            do
            { 
                lastasked++;
                lastasked = (lastasked >= p) ? 0 : lastasked;
            }
            while ( !ingame[lastasked] || lastasked == myIndex );

    	    RET = new Move(IFCConstants._CINTERROGATION, hlist, lastasked);
        }
        
        // Make a guess if we know what's hidden
        // if we're sure which are the hidden cards, then guess
        else
	    {
		    RET = new Move(IFCConstants._CGUESS, hlist);
	    }

        return RET;

    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
	    int i,j;
	    for(i=0, j=0;(i < cardlist.length) && (j < mycards.length);)
	    {
		    if(cardlist[i] == mycards[j])
				return cardlist[i];
		    if(cardlist[i] < mycards[j])
			    i++;
		    else
			    j++;
	    }
	    return 0;
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            hidden[response-1] = false;
        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            hidden[ cards[i]-1 ] = false;
        }
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getHidden();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    // Returns a list of cards that could be but are not necessarily hidden,
    // i.e. cards that are definitely not hidden are not in this list
    public int[] getHidden() throws Exception
    {
        // Count the number of possibly hidden cards left
        int count = 0;
        int[] hcards = new int[N];
        for ( int i = 0, j = 0; i < N; i++ )
        {
            if ( hidden[i] == true )
            {
                count++;
                hcards[j++] = i+1;
            }
        }
        int[] hlist = new int[count];
        System.arraycopy(hcards, 0, hlist, 0, count);

        return hlist;
    }

    public MoveResult[] getUpdates() throws Exception
    {
        int currturn = _clue.currRound();
        int elapsed = currturn - lastturn;
        if ( elapsed < 0 )
            elapsed = 0;
        
        MoveResult[] results = new MoveResult[elapsed];

        for ( int i = 0; i < elapsed; i++ )
        {
            results[i] = _clue.history(i+lastturn);
        }

        lastturn = currturn;

        return results;
    }

}

class WeightedPlayer extends CorePlayer
{

    PlayerNode[] players;
    boolean[] confirmed; // Keeps track of cards that have been confirmed hidden

    public WeightedPlayer(IFCPlayer handle)
    {
        super(handle);
    }

    public void register(Cluedo __noclue) throws Exception
    {
        super.register(__noclue);

        players = new PlayerNode[p];
        for ( int i = 0; i < p; i++ )
        {
            players[i] = new PlayerNode(i, N);
        }

        confirmed = new boolean[N];
    }

    public Move move() throws Exception
    {
        // Get updates
        MoveResult[] updates = getUpdates();
        
        updateData(updates);

        // Decide how to move
        Move RET = null;

        int[] known = Lib.getTrue(confirmed); 

        if ( known.length >= h )
        {
            // Make a guess
            RET = new Move(IFCConstants._CGUESS, known);
        }
        else
        {
            // Interrogate
            RET = super.move();

            // Add logic to narrow down what to ask about
            // Either aim for negative responses or
            // aim for positive responses 

            int[] hlist = getHidden();
    
            // Interrogate someone
    
            if ( hlist.length > h )
            {
                // ask players in sequence
                int[] asklist, overlap; 

                // ask about the overlap of asklist and hlist
                do
                { 
                    lastasked++;
                    lastasked = (lastasked >= p) ? 0 : lastasked;
                    asklist = players[lastasked].getUnconfirmedList();
                    overlap = Lib.overlap(asklist, hlist);
                }
                while ( !ingame[lastasked] || lastasked == myIndex || (N - asklist.length) >= k || overlap.length == 0 );
    
        	    RET = new Move(IFCConstants._CINTERROGATION, overlap, lastasked);
            }
            
            // Make a guess if we know what's hidden
            // if we're sure which are the hidden cards, then guess
            else
    	    {
    		    RET = new Move(IFCConstants._CGUESS, hlist);
    	    }

        }

        return RET;
    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
        return super.question(interrogator, cardlist);
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            hidden[response-1] = false;
            confirmPlayerCard( interrogatee, response );
        }
        else
        {
            players[interrogatee].removeCards(mycardlist);
        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            hidden[ cards[i]-1 ] = false;
            confirmPlayerCard( playerexited, cards[i] );
        }
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getHidden();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    private void updateData(MoveResult[] updates) throws Exception
    {
        // Update the card list info for all players
        for ( int i = 0; i < updates.length; i++ )
        {
            if ( updates[i].interrogatee != myIndex )
            {
                if ( updates[i].type == IFCConstants._CINTERROGATION )
                {
                    if ( updates[i].response == true )
                    {
                        players[updates[i].interrogatee].addWeight(updates[i].cardlist);
                    }
                    else
                    {
                        players[updates[i].interrogatee].removeCards(updates[i].cardlist);
                    }
                }
            }
        }

        // Check if any hidden cards can be deduced from the updates
        int[] hlist = getHidden();

        for ( int i = 0; i < hlist.length; i++ )
        {
            int card = hlist[i];
            boolean h = true;

            if ( !confirmed[card-1] )
            {
                for ( int j = 0; j < players.length; j++ )
                {
                    if ( j != myIndex && !players[j].notHave(card) )
                    {
                        h = false;
                        break;
                    }
                }
    
                if ( h == true )
                {
                    confirmed[card-1] = true;
                }
            }
        }

    }

    // Confirms a card for a player and negates it for all 
    // other players
    public void confirmPlayerCard( int player, int card )
    {
        players[player].confirmCard(card);

        // Set the status of this card to negative for all other players
        for ( int i = 0; i < p; i++ )
        {
            if ( i != player )
                players[i].removeCard(card);
        }
    }

}

class InverseWeightedPlayer extends CorePlayer
{

    PlayerNode[] players;
    boolean[] confirmed; // Keeps track of cards that have been confirmed hidden

    int rounds; // The max number of rounds before people will start guessing correctly
                // We wanna try to guess before this

    public InverseWeightedPlayer(IFCPlayer handle)
    {
        super(handle);
    }

    public void register(Cluedo __noclue) throws Exception
    {
        super.register(__noclue);

        players = new PlayerNode[p];
        for ( int i = 0; i < p; i++ )
        {
            players[i] = new PlayerNode(i, N);
        }

        confirmed = new boolean[N];
        rounds = (N - k - h) * p;
    }

    public Move move() throws Exception
    {
        // Get updates
        MoveResult[] updates = getUpdates();
        
        updateData(updates);

        // Decide how to move
        Move RET = null;

        int[] known = Lib.getTrue(confirmed); 

        if ( known.length >= h )
        {
            // Make a guess
            RET = new Move(IFCConstants._CGUESS, known);
        }
        else
        {
            // Interrogate
            RET = super.move();

            // Add logic to narrow down what to ask about
            // Either aim for negative responses or
            // aim for positive responses 

            int[] hlist = getHidden();
    
            // Interrogate someone
    
            if ( hlist.length > h && _clue.currRound() < (rounds * 4 / 5) )
            {
                // ask players in sequence
                int[] asklist, overlap; 

                // ask about the union of asklist and hlist
                do
                { 
                    lastasked++;
                    lastasked = (lastasked >= p) ? 0 : lastasked;
                    asklist = players[lastasked].getUnconfirmedList();
                    overlap = Lib.union(asklist, hlist);
                }
                while ( !ingame[lastasked] || lastasked == myIndex || (N - asklist.length) >= k || overlap.length == 0 );
    
        	    RET = new Move(IFCConstants._CINTERROGATION, overlap, lastasked);
            }
            
            // Make a guess if we know what's hidden
            // if we're sure which are the hidden cards, then guess
            // or in this case, we guess prematurely based on assumptions about other player's moves 
            else
    	    {
                // Make a final update based on the weights of the player
                if ( _clue.currRound() < (rounds * 4 / 5) )
                { 
                    for ( int i = 0; i < p; i++ )
                    {
                        players[i].assumeKnowledge(k);
                        for ( int j = 0; j < N; j++ )
                        {
                            if ( players[i].cardsInfo[j].weight == Double.POSITIVE_INFINITY )
                                hidden[ players[i].cardsInfo[j].card - 1 ] = false;
                        }
                    }   
                }
                
                // Remember to cut hlist down to size h first
                int[] flist = getHidden(); 

    		    RET = new Move(IFCConstants._CGUESS, flist);
    	    }

        }

        return RET;
    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
        return super.question(interrogator, cardlist);
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            hidden[response-1] = false;
            confirmPlayerCard( interrogatee, response );
        }
        else
        {
            players[interrogatee].removeCards(mycardlist);
        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            hidden[ cards[i]-1 ] = false;
            confirmPlayerCard( playerexited, cards[i] );
        }
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getHidden();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    private void updateData(MoveResult[] updates) throws Exception
    {
        // Update the card list info for all players
        for ( int i = 0; i < updates.length; i++ )
        {
            if ( updates[i].interrogatee != myIndex )
            {
                if ( updates[i].type == IFCConstants._CINTERROGATION )
                {
                    if ( updates[i].response == true )
                    {
                        players[updates[i].interrogatee].addWeight(updates[i].cardlist);
                    }
                    else
                    {
                        players[updates[i].interrogatee].removeCards(updates[i].cardlist);
                    }
                }
            }
        }

        // Check if any hidden cards can be deduced from the updates
        int[] hlist = getHidden();

        for ( int i = 0; i < hlist.length; i++ )
        {
            int card = hlist[i];
            boolean h = true;

            if ( !confirmed[card-1] )
            {
                for ( int j = 0; j < players.length; j++ )
                {
                    if ( j != myIndex && !players[j].notHave(card) )
                    {
                        h = false;
                        break;
                    }
                }
    
                if ( h == true )
                {
                    confirmed[card-1] = true;
                }
            }
        }

    }

    // Confirms a card for a player and negates it for all 
    // other players
    public void confirmPlayerCard( int player, int card )
    {
        players[player].confirmCard(card);

        // Set the status of this card to negative for all other players
        for ( int i = 0; i < p; i++ )
        {
            if ( i != player )
                players[i].removeCard(card);
        }
    }

}

class ConditionalPlayer extends CorePlayer
{

    PlayerNode[] players;
    boolean[] confirmed; // Keeps track of cards that have been confirmed hidden
    ArrayList conditionals; // Keeps a record of conditionals obtained from interrogations

    public ConditionalPlayer(IFCPlayer handle)
    {
        super(handle);
    }

    public void register(Cluedo __noclue) throws Exception
    {
        super.register(__noclue);

        players = new PlayerNode[p];
        for ( int i = 0; i < p; i++ )
        {
            players[i] = new PlayerNode(i, N);
        }

        confirmed = new boolean[N];
        conditionals = new ArrayList();

        for ( int i = 0; i < mycards.length; i++ )
        {
            confirmPlayerCard( myIndex, mycards[i] );
        }

    }

    public Move move() throws Exception
    {
        // Get updates
        MoveResult[] updates = getUpdates();
        
        updateData(updates);

        // Decide how to move
        Move RET = null;

        int[] known = Lib.getTrue(confirmed); 

        if ( known.length >= h )
        {
            // Make a guess
            RET = new Move(IFCConstants._CGUESS, known);
        }
        else
        {
            // Interrogate
            RET = super.move();

            // Add logic to narrow down what to ask about
            // Either aim for negative responses or
            // aim for positive responses 

            int[] hlist = getHidden();
    
            // Interrogate someone
    
            if ( hlist.length > h )
            {
                // ask players in sequence
                int[] asklist, overlap, mixedlist; 

                // ask about the overlap of asklist and hlist
                do
                { 
                    lastasked++;
                    lastasked = (lastasked >= p) ? 0 : lastasked;
//System.out.println("p = " + p + " lastasked = " + lastasked);                    
                    asklist = players[lastasked].getUnconfirmedList();
                    overlap = Lib.overlap(asklist, hlist);
//System.out.println("    N - asklist.length = " + (N - asklist.length) + " overlap.length = " + overlap.length);                    

                    // add cards we know are owned by other people
                    mixedlist = Lib.union(overlap, mycards);
                    //mixedlist = Lib.union(mixedlist, 

                }
                while ( !ingame[lastasked] || lastasked == myIndex || (N - asklist.length) >= k || overlap.length == 0 );
    
        	    //RET = new Move(IFCConstants._CINTERROGATION, mixedlist, lastasked);
        	    RET = new Move(IFCConstants._CINTERROGATION, mixedlist, lastasked);
            }
            
            // Make a guess if we know what's hidden
            // if we're sure which are the hidden cards, then guess
            else
    	    {
    		    RET = new Move(IFCConstants._CGUESS, hlist);
    	    }

        }

        return RET;
    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
        return super.question(interrogator, cardlist);
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            hidden[response-1] = false;
            confirmPlayerCard( interrogatee, response );
        }
        else
        {
            players[interrogatee].removeCards(mycardlist);
        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            hidden[ cards[i]-1 ] = false;
            confirmPlayerCard( playerexited, cards[i] );
        }
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getHidden();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    private void updateData(MoveResult[] updates) throws Exception
    {
        // Update the card list info for all players
        for ( int i = 0; i < updates.length; i++ )
        {
            if ( updates[i].type == IFCConstants._CINTERROGATION )
            {
                if ( updates[i].interrogatee != myIndex )
                {
                    if ( updates[i].response == true )
                    {
                        players[updates[i].interrogatee].addWeight(updates[i].cardlist);
                    }
                    else
                    {
                        players[updates[i].interrogatee].removeCards(updates[i].cardlist);
                    }

                    // Add the move to the list of conditionals
                    // Right now there's no check to see if a conditional is already in the list
                    // maybe we'll add it later if we have time 

                    // First we get the old conditionals in array form
                    Conditional[] archive = (Conditional[])(conditionals.toArray( new Conditional[0] ));

                    // Get the new conditional
                    Conditional newCond = new Conditional( updates[i].interrogatee, (int[])(updates[i].cardlist.clone()), updates[i].response ); 

                    if ( newCond.getSize() > 1 )
                        conditionals.add( newCond );

                    // For every new conditional, we combine it with the old list and see if we produce anything new 
                    Conditional[] combined = newCond.combineConditionalList( archive );
                    
                    for ( int j = 0; j < combined.length; j++ )
                    {
                        // No need to update for newly produced negative conditions because they were already updated
                        // for the responses they came from

                        // We can only really update for positive responses of size 1
                        if ( combined[j].getSize() == 1 )
                        {
                            if ( combined[j].getCondition() == true && combined[j].getPlayer() != myIndex )
                            {
                                players[ combined[j].getPlayer() ].confirmCard( (combined[j].getMembers())[0] );
                                hidden[ (combined[j].getMembers())[0] - 1 ] = false;
                            }
                        }
                        else if ( combined[j].getSize() == 2 )
                        {
                            if ( combined[j].getPlayer() == -1 )
                            {
                                int[] members = combined[j].getMembers();
                                hidden[ members[0] - 1 ] = false;
                                hidden[ members[1] - 1 ] = false;
                            }
                        }
                        else
                        {
                            conditionals.add( combined[j] );
                        }

                    }

                }
            }
        }

        // Re-evaluate all conditionals against the positive and negative lists to see if we get anything new


        // Check if any hidden cards can be deduced from the updates
        int[] hlist = getHidden();

        for ( int i = 0; i < hlist.length; i++ )
        {
            int card = hlist[i];
            boolean h = true;

            if ( !confirmed[card-1] )
            {
                for ( int j = 0; j < players.length; j++ )
                {
                    if ( j != myIndex && !players[j].notHave(card) )
                    {
                        h = false;
                        break;
                    }
                }
    
                if ( h == true )
                {
                    confirmed[card-1] = true;
                }
            }
        }

    }

    // Confirms a card for a player and negates it for all 
    // other players
    public void confirmPlayerCard( int player, int card )
    {
        players[player].confirmCard(card);

        hidden[card-1] = false;

        // Set the status of this card to negative for all other players
        for ( int i = 0; i < p; i++ )
        {
            if ( i != player )
                players[i].removeCard(card);
        }
    }

}



class StatisticalPlayer
{
    Random _random;

    int N;  // total number of cards
    int k;  // number of cards per player
    int h;  // number of hidden cards
    int p;  // number of players
    int myIndex;

    Cluedo _clue;

    int[] mycards; // list of my cards

    IFCPlayer _this_; // handle to the instance of my player

    boolean[] ingame; // keeps track of players still in the game

    boolean[] negative; // keeps track of which cards we definitely know aren't hidden
                        // true = definitely not hidden, false = we don't know
    boolean[] positive; // keeps track of which cards we definitely know are hidden
                        // true = definitely hidden, false = we don't know

    int lastasked; // the player we last interrogated

    int lastturn; // this is the turn on which we last refreshed our info about opponents moves

    PlayerNode[] players; // we use this to keep track of the number of times a player has asked
                          // for each card

    int rounds;

    public StatisticalPlayer(IFCPlayer handle)
    {
        _this_ = handle;
    }

    public void register(Cluedo __noclue) throws Exception 
    {
        _random = new Random();

	    _clue = __noclue;
	    N = __noclue.numCards();
	    k = __noclue.numCardsPerPlayer();
	    h = __noclue.numHiddenCards();
	    p = __noclue.numPlayers();
	    myIndex = __noclue.indexOf(_this_);

        mycards = __noclue.getPlayerList(myIndex, _this_);

        ingame = new boolean[p];
        for ( int i = 0; i < p; i++ )
        {
            ingame[i] = true;
        }

        negative = new boolean[N];
        positive = new boolean[N];
        for ( int i = 0; i < mycards.length; i++ )
        {
            negative[ mycards[i]-1 ] = true;
        }

        lastturn = 1;

        players = new PlayerNode[p];
        for ( int i = 0; i < p; i++ )
        {
            players[i] = new PlayerNode(i, N);
        }

        rounds = (N - k - h) * p * 3 / 4;
    }

    public Move move() throws Exception 
    {
        Move RET = null;

        int[] hlist = getFalseNegatives();

        // Interrogate someone

        if ( hlist.length > h )
        {
            if ( _clue.currRound() < rounds )
            {
                // ask players in sequence
                do
                { 
                    lastasked++;
                    lastasked = (lastasked >= p) ? 0 : lastasked;
                }
                while ( !ingame[lastasked] || lastasked == myIndex );
    
        	    RET = new Move(IFCConstants._CINTERROGATION, hlist, lastasked);
            }
            else
            {
                // If its guessing time... we mine the history for info and guess

                MoveResult[] history = getHistory();

                PlayerNode.InfoNode[] cardCount = new PlayerNode.InfoNode[N];
                for ( int i = 0; i < N; i++ )
                {
                    cardCount[i] = new PlayerNode.InfoNode();
                    cardCount[i].card = i+1;
                    cardCount[i].weight = 0;
                }

                for ( int i = 0; i < history.length; i++ )
                {
                    int[] list = history[i].cardlist;

                    for ( int j = 0; j < list.length; j++ )
                    {
                        (cardCount[ list[j] - 1 ]).weight++;
                    }
                }
                
                Lib.quicksort(cardCount);

                int[] asklist = new int[h];

                for ( int i = 0; i < h; i++ )
                {
                    asklist[i] = cardCount[ N-i-1 ].card;
                }

                RET = new Move(IFCConstants._CGUESS, asklist);
                
            }
        }
        
        // Make a guess if we know what's hidden
        // if we're sure which are the hidden cards, then guess
        else
	    {
		    RET = new Move(IFCConstants._CGUESS, hlist);
	    }

        return RET;

    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
	    int i,j;
	    for(i=0, j=0;(i < cardlist.length) && (j < mycards.length);)
	    {
		    if(cardlist[i] == mycards[j])
				return cardlist[i];
		    if(cardlist[i] < mycards[j])
			    i++;
		    else
			    j++;
	    }
	    return 0;
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            negative[response-1] = true;
        }
        else
        {

        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            negative[ cards[i]-1 ] = true;
        }
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getFalseNegatives();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    public int[] getTrueNegatives() throws Exception
    {
        return Lib.getTrue(negative);
    }

    public int[] getTruePositives() throws Exception
    {
        return Lib.getTrue(positive);
    }

    public int[] getFalseNegatives() throws Exception
    {
        return Lib.getFalse(negative);
    }

    public int[] getFalsePositives() throws Exception
    {
        return Lib.getFalse(positive);
    }

    public MoveResult[] getUpdates() throws Exception
    {
        int currturn = _clue.currRound();
        int elapsed = currturn - lastturn;
        if ( elapsed < 0 )
            elapsed = 0;
        
        MoveResult[] results = new MoveResult[elapsed];

        for ( int i = 0; i < elapsed; i++ )
        {
            results[i] = _clue.history(i+lastturn);
        }

        lastturn = currturn;

        return results;
    }

    public MoveResult[] getHistory() throws Exception
    {
        int curr = _clue.currRound();

        MoveResult[] history = new MoveResult[curr-1];

        for ( int i = 0; i < curr-1; i++ )
        {
            history[i] = _clue.history(i+1);
        }

        return history;
    }

    public void updateData( MoveResult[] updates )
    {
        for ( int i = 0; i < updates.length; i++ )
        {
        }

    }

}



class CombinedPlayer extends CorePlayer
{

    PlayerNode[] players;
    boolean[] confirmed; // Keeps track of cards that have been confirmed hidden
    ArrayList conditionals; // Keeps a record of conditionals obtained from interrogations

    int rounds;

    public CombinedPlayer(IFCPlayer handle)
    {
        super(handle);
    }

    public void register(Cluedo __noclue) throws Exception
    {
        super.register(__noclue);

        players = new PlayerNode[p];
        for ( int i = 0; i < p; i++ )
        {
            players[i] = new PlayerNode(i, N);
        }

        confirmed = new boolean[N];
        conditionals = new ArrayList();

        for ( int i = 0; i < mycards.length; i++ )
        {
            confirmPlayerCard( myIndex, mycards[i] );
        }

        rounds = (N - k - h) * p * 2 / 3; // this is when we will guess
    }

    int[] stored;
    int[] uncertain;

    public Move move() throws Exception
    {
        // Get updates
        MoveResult[] updates = getUpdates();
        
        updateData(updates);

        // Decide how to move
        Move RET = null;

        int[] known = Lib.getTrue(confirmed); 

        if ( known.length >= h )
        {
            // Make a guess
            RET = new Move(IFCConstants._CGUESS, known);
        }
        else
        {
            // Interrogate
            RET = super.move();

            // Add logic to narrow down what to ask about
            // Either aim for negative responses or
            // aim for positive responses 

            int[] hlist = getHidden();
    
            // Interrogate someone
    
            if ( hlist.length > h )
            {
                if ( _clue.currRound() < rounds )
                {
                    // ask players in sequence
                    int[] asklist, overlap, mixedlist; 
    
                    // ask about the overlap of asklist and hlist
                    do
                    { 
                        lastasked++;
                        lastasked = (lastasked >= p) ? 0 : lastasked;
    //System.out.println("p = " + p + " lastasked = " + lastasked);                    
                        asklist = players[lastasked].getUnconfirmedList();
                        overlap = Lib.overlap(asklist, hlist);
    //System.out.println("    N - asklist.length = " + (N - asklist.length) + " overlap.length = " + overlap.length);                    
    
                        // add cards we know are owned by other people
                        mixedlist = Lib.overlap(overlap, mycards);
                        mixedlist = Lib.combine(mixedlist, hlist);
                        mixedlist = Lib.combine(mixedlist, hlist);
                        mixedlist = Lib.combine(mixedlist, hlist);
                        mixedlist = Lib.combine(mixedlist, hlist);
                        mixedlist = Lib.combine(mixedlist, hlist);
                        mixedlist = Lib.combine(mixedlist, hlist);
    
                    }
                    while ( !ingame[lastasked] || lastasked == myIndex || (N - asklist.length) >= k || overlap.length == 0 );
        
            	    //RET = new Move(IFCConstants._CINTERROGATION, mixedlist, lastasked);
            	    RET = new Move(IFCConstants._CINTERROGATION, mixedlist, lastasked);
                 } 
    
                 else
                 {
                    // If its guessing time... we mine the history for info and guess
    
                    MoveResult[] history = getHistory();
    
                    // Figure out what cards were asked about the most overall
                    PlayerNode.InfoNode[] cardCount = new PlayerNode.InfoNode[N];
                    for ( int i = 0; i < N; i++ )
                    {
                        cardCount[i] = new PlayerNode.InfoNode();
                        cardCount[i].card = i+1;
                        cardCount[i].weight = 0;
                    }
    
                    for ( int i = 0; i < history.length; i++ )
                    {
                        int[] list = history[i].cardlist;
    
                        if ( list != null )
                        {
                            for ( int j = 0; j < list.length; j++ )
                            {
                                (cardCount[ list[j] - 1 ]).weight++;
                            }
                        }
                    }
                    
                    Lib.quicksort(cardCount);
    
                    int[] asklist = new int[h];
    
                    for ( int i = 0, j = N-1; i < h; i++ )
                    {
                        int tmp = cardCount[j].card;

                        while ( hidden[tmp-1] == false )
                        {
                            tmp = cardCount[--j].card;
                        }

                        asklist[i] = tmp;
                        j--;

                    }
    
                    stored = asklist;
                    uncertain = Lib.difference(stored, known);
/*
System.out.println("\n\n=======Uncertain: ");
for ( int k = 0; k < uncertain.length; k++ )
System.out.print(uncertain[k] + ", ");
System.out.println("\n\n");
*/

/*
                    // Figure out what each player got asked least about
                    PlayerNode[] leastCards = new PlayerNode[p];
                    for ( int i = 0; i < p; i++ )
                    {
                        leastCards[i] = new PlayerNode(i, N);
                    }

                    for ( int i = 0; i < history.length; i++ )
                    {
                        int[] list = history[i].cardlist;
                        int playerIndex = history[i].interrogatee;

                        if ( list != null )
                        {
                            for ( int j = 0; j < list.length; j++ )
                            {
                                leastCards[ playerIndex ].incWeight( list[j] );
                            }
                            
                        }
                    }

                    int least = k / 3; // Check only the smallest "least" number of cards
*/
/*                   
System.out.println("\n-----Least: ");
for ( int m = 0; m < p; m++ )
{
    System.out.print("--player " + m + ":  ");
    PlayerNode.InfoNode[] tmp = players[m].cardsInfo;
    Lib.quicksort(tmp);
    for ( int l = 0; l < N; l++ )
    {
        System.out.print( tmp[l].card + "/" + (double)(tmp[l].weight) + "  ;  " );
    }
    System.out.println();
}
*/
                    
/*
System.out.println("\n-----Least: ");
for ( int m = 0; m < p; m++ )
{
    System.out.print("--player " + m + ":  ");
    PlayerNode.InfoNode[] tmp = leastCards[m].cardsInfo;
    Lib.quicksort(tmp);
    for ( int l = 0; l < N; l++ )
    {
        System.out.print( tmp[l].card + "/" + (int)(tmp[l].weight) + "  ;  " );
    }
    System.out.println();
}
*/

                    RET = new Move(IFCConstants._CGUESS, asklist);
                    
                }
               
            }
            
            // Make a guess if we know what's hidden
            // if we're sure which are the hidden cards, then guess
            else
    	    {
    		    RET = new Move(IFCConstants._CGUESS, hlist);
    	    }

        }

        return RET;
    }

    // Default answer to questioning is to show first card that matches
    public int question(int interrogator, int[] cardlist) throws Exception
    {
        return super.question(interrogator, cardlist);
    }
    
    public void answer(int interrogatee, int[] mycardlist, int response) throws Exception
    {
        if ( response > 0 )
        {
            hidden[response-1] = false;
            confirmPlayerCard( interrogatee, response );
        }
        else
        {
            players[interrogatee].removeCards(mycardlist);
        }
    }
    
    public void notifyPlayerExit(int playerexited) throws Exception
    {
        ingame[playerexited] = false;

        int[] cards = _clue.getPlayerList(playerexited, null);
        for ( int i = 0; i < cards.length; i++ )
        {
            hidden[ cards[i]-1 ] = false;
            confirmPlayerCard( playerexited, cards[i] );
        }

        rounds = rounds * 9 / 10;
    }
    
    // Default forced guess is first h cards from hidden list
    public int[] forcedGuess() throws Exception
    {
	    int[] finallist = new int[h];
        int[] hlist = getHidden();

        System.arraycopy(hlist, 0, finallist, 0, h);

	    return finallist;
    }

    private void updateData(MoveResult[] updates) throws Exception
    {
        // Update the card list info for all players
        for ( int i = 0; i < updates.length; i++ )
        {
            if ( updates[i].type == IFCConstants._CINTERROGATION )
            {
                if ( updates[i].interrogatee != myIndex )
                {
                    if ( updates[i].response == true )
                    {
                        players[updates[i].interrogatee].addWeight(updates[i].cardlist);
                    }
                    else
                    {
                        players[updates[i].interrogatee].removeCards(updates[i].cardlist);
                    }

                    // Add the move to the list of conditionals
                    // Right now there's no check to see if a conditional is already in the list
                    // maybe we'll add it later if we have time 

                    // First we get the old conditionals in array form
                    Conditional[] archive = (Conditional[])(conditionals.toArray( new Conditional[0] ));

                    // Get the new conditional
                    Conditional newCond = new Conditional( updates[i].interrogatee, (int[])(updates[i].cardlist.clone()), updates[i].response ); 

                    if ( newCond.getSize() > 1 )
                        conditionals.add( newCond );

                    // For every new conditional, we combine it with the old list and see if we produce anything new 
                    Conditional[] combined = newCond.combineConditionalList( archive );
                    
                    for ( int j = 0; j < combined.length; j++ )
                    {
                        // No need to update for newly produced negative conditions because they were already updated
                        // for the responses they came from

                        // We can only really update for positive responses of size 1
                        if ( combined[j].getSize() == 1 )
                        {
                            if ( combined[j].getCondition() == true && combined[j].getPlayer() != myIndex )
                            {
                                players[ combined[j].getPlayer() ].confirmCard( (combined[j].getMembers())[0] );
                                hidden[ (combined[j].getMembers())[0] - 1 ] = false;
                            }
                        }
                        else if ( combined[j].getSize() == 2 )
                        {
                            if ( combined[j].getPlayer() == -1 )
                            {
                                int[] members = combined[j].getMembers();
                                hidden[ members[0] - 1 ] = false;
                                hidden[ members[1] - 1 ] = false;
                            }
                        }
                        else
                        {
                            conditionals.add( combined[j] );
                        }

                    }

                }
            }
        }

        // Re-evaluate all conditionals against the positive and negative lists to see if we get anything new


        // Check if any hidden cards can be deduced from the updates
        int[] hlist = getHidden();

        for ( int i = 0; i < hlist.length; i++ )
        {
            int card = hlist[i];
            boolean h = true;

            if ( !confirmed[card-1] )
            {
                for ( int j = 0; j < players.length; j++ )
                {
                    if ( j != myIndex && !players[j].notHave(card) )
                    {
                        h = false;
                        break;
                    }
                }
    
                if ( h == true )
                {
                    confirmed[card-1] = true;
                }
            }
        }

    }

    // Confirms a card for a player and negates it for all 
    // other players
    public void confirmPlayerCard( int player, int card )
    {
        players[player].confirmCard(card);

        hidden[card-1] = false;

        // Set the status of this card to negative for all other players
        for ( int i = 0; i < p; i++ )
        {
            if ( i != player )
                players[i].removeCard(card);
        }
    }

    public MoveResult[] getHistory() throws Exception
    {
        int curr = _clue.currRound();

        MoveResult[] history = new MoveResult[curr-2];

        for ( int i = 0; i < curr-2; i++ )
        {
            history[i] = _clue.history(i+1);
        }

        return history;
    }

}
