public interface IBettingStrategy {
    
    public IAction action(ITable table, List<Card> holeCards, IPlayerData plData, IActionBuilder actionBuild, IHandEvaluator eval);
    
} public interface IHandEvaluator {
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers);
    
    public double effectiveHandStrength();
    public double rawHandStrength();
    public double posPotential();
    public double negPotential();
    } public interface IHandRange {
    public double handWeight(Card c1, Card c2);
} public class PlayerAgent implements IPlayer, IGameEventListener {
    
    private String _playerId;
    private IHandEvaluator _eval;
    private IHandRange _compositeRange;
    private IBettingStrategy _strat;
    private List<Card> _holeCards;
    private int _stack;
    private int _preRoundStack;
    private int _winnings;
    
    public PlayerAgent(ITable table, String id, IHandEvaluator eval, IHandRange fieldRange, IBettingStrategy strat) {
        _playerId = id;
        _eval = eval;
        _compositeRange = fieldRange;
        _strat = strat;
        _holeCards = new ArrayList<>(2);
        table.registerEventListener(this);
    }
    
    public IBettingStrategy bettingStrategy() {
        return _strat;
    }

    @Override
    public String id() {
        return _playerId;
    }

    @Override
    public void dealCard(Card c) {
        _holeCards.add(c);
    }
    
    @Override
    public List<Card> holeCards() {
        return _holeCards;
    }

    @Override
    public IAction action(ITable table, IPlayerData plData, IActionBuilder actionBuild) {
        _eval.compute(_holeCards, table.board(), _compositeRange, table.numberActivePlayers());
        
        
        System.out.println("((( AGENT ACTION: " + id() + " )))");
        System.out.print("Hand: ");
        for (Card c : _holeCards) {
            System.out.print(" [" + c.toString() + "] ");
        }
        System.out.println();
        
        System.out.println("EHS: " + _eval.effectiveHandStrength());
        System.out.println("RawStr: " + _eval.rawHandStrength());
        System.out.println("PosPot: " + _eval.posPotential());
        
        System.out.println("negPot: " + _eval.negPotential());
        
        
        return _strat.action(table, _holeCards, plData, actionBuild, _eval);
    }

    @Override
    public void roundBegin(ITable table, ITable.Round r) {
        _preRoundStack = _stack;
    }

    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        int diff = _stack - _preRoundStack;
        if (diff > 0) {
            _winnings += diff;
        }
    }

    @Override
    public void newHand(ITable table) {
        _holeCards.clear();
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }

    @Override
    public void setStack(int stack) {
        _stack = stack;
    }

    @Override
    public int stack() {
        return _stack;
    }
    
    public int winnings() {
        return _winnings;
    }
} public class NeuralNetStrategy implements IBettingStrategy, IGameEventListener {
    
    private Network _net;
    private boolean _mainBetter;
    private double _handImage;
    
    public NeuralNetStrategy(ITable table) {
        table.registerEventListener(this);
    }

    @Override
    public IAction action(ITable table, List<Card> holeCards, IPlayerData plData, IActionBuilder actionBuild, IHandEvaluator eval) {
        
        // Effective Hand Strength
        _net.input[0] = (float) eval.rawHandStrength();
        
        // Positive potential, treated separately from RawStr to encourage the nets to pick up on things like potential vs pot odds
        _net.input[1] = (float) eval.posPotential();
        
        // Negative Potential
        _net.input[2] = (float) eval.negPotential();
        
        // Pot Odds
        _net.input[3] = (float) plData.potOdds(table.potSize());
        
        // Position based on the dealer button (small blind = 1, big blind = 2, UTG = 3, ...)
        // Ratio by number active players to achieve a measure [0..1] with 1.0 being last position
        // _net.input[4] = ((float) plData.position()) / ((float) table.numberActivePlayers());
        // Discarded, redundant (See proportion of active players which have already acted, better measure)
        
        // Proportion of the players which are still active
        _net.input[4] = ((float) table.numberActivePlayers()) / ((float) table.numberPlayers());
        
        // Proportion of the active players which have already acted
        _net.input[5] = ((float) (table.numberActivePlayers() - table.numberPlayersToAct())) / ((float) table.numberActivePlayers());
        
        // Number of bets in the round, maxed at 2
        int numBets = table.numberBets();
        if (numBets > 2) {
            numBets = 2;
        }
        _net.input[6] = ((float) numBets) / 2.0f;
        
        // Image at the table
        // Measure ranging from [0..1], 0.0 being weakest and 1.0 strongest
        // Compound measure computed from boolean value _mainBetter each round
        // with a decreasing weight the older the round is
        // At turn [0.75] = 0.25(mainBetter preflop) + 0.5(mainBetter flop)
        // At river [0.375] = 0.125(mainBetter preflop) + 0.25(mainBetter flop)
        // etc.
        _net.input[7] = (float) _handImage;
        
        // Proportion of the pot contributed by the agent
        _net.input[8] = ((float) plData.totalMoneyInPot()) / ((float) table.potSize());
        
        // Current round
        // Measure ranging from [0..1] with 0.0 being preflop and river being 1.0
        _net.input[9] = ((float) table.round().ordinal()) / 3.0f;
        
        Sane_NN.Activate_net(_net);
        
        if (_net.winner == 2 && table.numberBets() < table.maxBets()) {
            _mainBetter = true;
            return actionBuild.makeRaise(actionBuild.minRaise());
        }
        else if (_net.winner >= 1 || plData.amountToCall() == 0) {
            _mainBetter = false;
            return actionBuild.makeCall();
        }
        return actionBuild.makeFold();
    }
    
    public void setNetwork(Network net) {
        _net = net;
    }
    
    public void networkFromFile(String name) {
        Neuron npop[] = new Neuron[Config.NUM_HIDDEN];
        for (int i = 0; i < Config.NUM_HIDDEN; i++) {
            npop[i] = new Neuron();
        }
        Network net = new Network();
        String file = name + ".bin";
        Sane_Util.Load_partial(npop, file);
        Sane_NN.Build_net(net, npop);
        setNetwork(net);
    }

    @Override
    public void roundBegin(ITable table, ITable.Round r) {
        _mainBetter = false;
    }

    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        _handImage /= 2;
        if (_mainBetter) {
            _handImage += 0.5;
        }
    }

    @Override
    public void newHand(ITable table) {
        _handImage = 0;
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }
    
} public class SimpleStrategy implements IBettingStrategy, IGameEventListener {
    
    private static final double VALUE_2BET = 0.85;
    private static final double VALUE_1BET = 0.50;
    
    private static final double BLINDSTEAL_RATE = 0.35;
    private static final double PREFLOP_LATE_LIMPS_RATE = 0.6;
    private static final double CONTINUATION_BET_RATE = 0.3;
    private static final double FLOP_POT_STEAL_RATE = 0.3;
    
    private boolean _preflopBetter;
    
    public SimpleStrategy(ITable table) {
        table.registerEventListener(this);
    }

    private IAction doBets(ITable table, IPlayerData plData, IActionBuilder actionBuild, int n) {
        if (table.numberBets() < n) {
            //System.out.println("Decision: VALUE BET");
            return actionBuild.makeRaise(actionBuild.minRaise());
        }
        else if (plData.moneyInPotForRound() > 0 || plData.betsToCall() <= n) {
            //System.out.println("Decision: VALUE CALL");
            _preflopBetter = false;
            return actionBuild.makeCall();
        }
        _preflopBetter = false;
        return actionBuild.makeFold();
    }
    
    @Override
    public IAction action(ITable table, List<Card> holeCards, IPlayerData plData, IActionBuilder actionBuild, IHandEvaluator eval) {
        
        Card c1 = holeCards.get(0);
        Card c2 = holeCards.get(1);
        
        if (c1.rank().ordinal() < c2.rank().ordinal()) {
            Card tmp = c1;
            c1 = c2;
            c2 = tmp;
        }
        
        // Variable malus [0.0..0.1] for early positions
        double positionNeg = 0.10 * (((double)table.numberPlayersToAct()) / ((double) table.numberActivePlayers() - 1));
        
        // Value bets
        if (eval.effectiveHandStrength() - positionNeg >= VALUE_2BET) {
            if (table.round() == Round.PREFLOP) {
                _preflopBetter = true;
            }
            return doBets(table, plData, actionBuild, 2);
        }
        else if (eval.effectiveHandStrength() - positionNeg >= VALUE_1BET) {
            if (table.round() == Round.PREFLOP) {
                _preflopBetter = true;
            }
            return doBets(table, plData, actionBuild, 1);
        }
        
        if (table.round() == Round.PREFLOP) {
            _preflopBetter = false;
            if (plData.position() == table.numberActivePlayers()) {
                // On the dealer button
                if (table.numberBets() == 0) {
                    // No action
                    if (Math.random() <= BLINDSTEAL_RATE) {
                        // Try to steal blinds 35% of the time
                        _preflopBetter = true;
                        //System.out.println("Decision: BLIND STEAL");
                        return actionBuild.makeRaise(actionBuild.minRaise());
                    }
                }
            }
            if (table.numberPlayersToAct() <= 1 && table.numberCallers() >= 2 && table.numberBets() <= 1) {
                if (c1.rank() == c2.rank()
                        || (c1.suit() == c2.suit()
                        && c1.rank().ordinal() == c2.rank().ordinal() + 1)) {
                    // Pocket pair or suited connectors
                    if (Math.random() <= PREFLOP_LATE_LIMPS_RATE) {
                        // Limp to try to flop a monster hand 60% of the time
                        //System.out.println("Decision: LATE CHEAP LIMP");
                        return actionBuild.makeCall();
                    }
                }
            }
        }
        else if (table.round() == Round.FLOP) {
            if (table.numberBets() == 0) {
                if (_preflopBetter) {
                    // Strong preflop image, no action, try a continuation bet 30% of the time
                    if (Math.random() <= CONTINUATION_BET_RATE) {
                        //System.out.println("Decision: CONTINUATION BET");
                        return actionBuild.makeRaise(actionBuild.minRaise());
                    }
                }
                if (plData.position() == table.numberActivePlayers()) {
                    // Last position, action checked to us
                    if (table.potSize() >= 5 * actionBuild.minRaise()) {
                        // Decent pot size
                        if (Math.random() <= FLOP_POT_STEAL_RATE) {
                            // Try to steal the pot 30% of the time
                            //System.out.println("Decision: POT STEAL");
                            return actionBuild.makeRaise(actionBuild.minRaise());
                        }
                    }
                }
            }
        }
        
        // Free check
        if (plData.amountToCall() == 0) {
            //System.out.println("Decision: FREE CHECK");
            return actionBuild.makeCall();
        }
        
        // Drawing hands, check pot odds
        if ((table.round() != Round.RIVER && eval.posPotential() >= plData.potOdds(table.potSize()))
                || eval.effectiveHandStrength() >= plData.potOdds(table.potSize())) {
            //System.out.println("Decision: DRAWING HAND / POT ODDS");
            return actionBuild.makeCall();
        }
        
        if (Math.random() <= (((double) plData.totalMoneyInPot()) / ((double) table.potSize())) / 1.5) {
            // Make the call now and then, depending on the amount already invested in the pot
            return actionBuild.makeCall();
        }
        
        // Last resort, fold the hand
        return actionBuild.makeFold();
    }

    @Override
    public void roundBegin(ITable table, ITable.Round r) {
        
    }

    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        
    }

    @Override
    public void newHand(ITable table) {
        _preflopBetter = false;
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }
    
} public class CompoundHandEval implements IHandEvaluator {
    private IHandEvaluator _preflop;
    private IHandEvaluator _postflop;
    private IHandEvaluator _lastCall;
    
    public CompoundHandEval(IHandEvaluator preflop, IHandEvaluator postflop) {
        _preflop = preflop;
        _postflop = postflop;
    }

    @Override
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers) {
        _lastCall = (board.isEmpty() ? _preflop : _postflop);
        _lastCall.compute(holecards, board, range, numPlayers);
    }

    @Override
    public double effectiveHandStrength() {
        return _lastCall.effectiveHandStrength();
    }
    
    @Override
    public double rawHandStrength() {
        return _lastCall.rawHandStrength();
    }
    
    @Override
    public double posPotential() {
        return _lastCall.posPotential();
    }

    @Override
    public double negPotential() {
        return _lastCall.negPotential();
    }
} public class PreflopHandGroups extends AHandEvaluator {

    private static int[][] _handGroups = {
        {1, 1, 2, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5},
        {2, 1, 2, 3, 4, 6, 7, 7, 7, 7, 7, 7, 7},
        {3, 4, 1, 3, 4, 5, 7, 9, 9, 9, 9, 9, 9},
        {4, 5, 5, 1, 3, 4, 6, 8, 9, 9, 9, 9, 9},
        {6, 6, 6, 5, 2, 4, 5, 7, 9, 9, 9, 9, 9},
        {8, 8, 8, 7, 7, 3, 4, 5, 8, 9, 9, 9, 9},
        {9, 9, 9, 8, 8, 7, 4, 5, 6, 8, 9, 9, 9},
        {9, 9, 9, 9, 9, 9, 8, 5, 5, 6, 8, 9, 9},
        {9, 9, 9, 9, 9, 9, 9, 8, 6, 7, 7, 9, 9},
        {9, 9, 9, 9, 9, 9, 9, 9, 8, 6, 6, 7, 9},
        {9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 7, 8},
        {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 8},
        {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7}
    };
    
    private static int[] _groupCount = {
        5, 5, 6, 8, 17, 10, 19, 15, 84
    };

    @Override
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers) {

        Card c1 = holecards.get(0);
        Card c2 = holecards.get(1);
        
        if (c1.rank().ordinal() > c2.rank().ordinal()) {
            Card tmp = c1;
            c1 = c2;
            c2 = tmp;
        }
        
        if (c1.suit() == c2.suit()) {
            Card tmp = c1;
            c1 = c2;
            c2 = tmp;
        }
        
        int group = _handGroups[Rank.values().length - c1.rank().ordinal() - 1][Rank.values().length - c2.rank().ordinal() - 1];
        double count = 0;
        
        for (int i = 0; i < group - 1; i++) {
            count += _groupCount[i];
        }
        
        count += ((double) _groupCount[group - 1]) / 2.0;
        
        _handStr = 0.9 - count / 169.0;
        _handStr = Math.pow(_handStr, numPlayers - 1);
    }
} public class HeuristicPPotEval extends AEnumHandEvaluator {
    
    @Override
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers) {
        initArrays();
        
        IHand ownCurrentHand = PokerFactory.buildHand(holecards, board);

        List<EnumCandidate> holeCandidates = EnumCandidate.buildCandidates(holecards, board);
                
        for (EnumCandidate cand : holeCandidates) {

            IHand oppCurentHand = PokerFactory.buildHand(cand.cards(), board);
            int curIdx = ownCurrentHand.compareTo(oppCurentHand) + 1;
            HPTotal[curIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
        }
        
        _numPl = numPlayers;
        calculatePotentials();

        if (board.size() < 5) {
            
            int[] suits = new int[Suit.values().length];
            int[] ranks = new int[Rank.values().length];
            
            int outs = 0;
            int flushes = 0;
            
            for (Card c : holecards) {
                suits[c.suit().ordinal()]++;
                ranks[c.rank().ordinal()]++;
            }
            for (Card c : board) {
                suits[c.suit().ordinal()]++;
                ranks[c.rank().ordinal()]++;
            }
            
            for (int i = 0; i < suits.length; i++) {
                if (suits[i] == 4) {
                    flushes++;
                    outs += 9;
                }
            }
            
            for (int i = Rank.DEUCE.ordinal(); i < Rank.JACK.ordinal(); i++) {
                if (ranks[i] >= 1
                        && ranks[i + 1] >= 1
                        && ranks[i + 2] >= 1
                        && ranks[i + 3] >= 1) {
                    outs += 8 - flushes * 2;
                }
            }
            
            if (holecards.get(0).rank() == holecards.get(1).rank()) {
                int onBoard = 0;
                for (Card c : board) {
                    if (c.rank() == holecards.get(0).rank()) {
                        onBoard++;
                    }
                }
                outs += 2 - onBoard;
                if (ownCurrentHand.type() == HandType.THREE_OF_A_KIND) {
                    outs += 8;
                    if (board.size() == 4) {
                        outs++;
                    }
                }
            }
            
            if (board.size() == 4) {
                _posPot = ((double) outs) * 2.0 / 100.0;
            }
            else {
                if (outs < 10) {
                    _posPot = ((double) outs) * 4.0 / 100.0;
                }
                else {
                    _posPot = (((double) outs) * 3.0 + 9.0) / 100.0;
                }                
            }
        }
        
        //printData();
    }
    
} public class RawStrEval extends AEnumHandEvaluator {
     @Override
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers) {
        initArrays();
        
        List<Card> boardCpy = new ArrayList(board);
        
        IHand ownCurrentHand = PokerFactory.buildHand(holecards, board);
        
        List<Card> drawCandidates = new ArrayList<>(Card.values());
        drawCandidates.removeAll(boardCpy);
        drawCandidates.removeAll(holecards);
        
        List<EnumCandidate> holeCandidates = EnumCandidate.buildCandidates(holecards, board);
                
        for (EnumCandidate cand : holeCandidates) {

            IHand oppCurentHand = PokerFactory.buildHand(cand.cards(), board);
            int curIdx = ownCurrentHand.compareTo(oppCurentHand) + 1;
            HPTotal[curIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
            
        }
        
        _numPl = numPlayers;
        calculatePotentials();

        //printData();
    }
    
} public class BucketEnum1CardHandEval extends AEnumHandEvaluator {
    private static final double POTENTIAL_COMP_RATIO = 1.6;
   
    @Override
    public void compute(List<Card> holecards, List<Card> board, IHandRange range, int numPlayers) {
        initArrays();
        
        List<Card> boardCpy = new ArrayList(board);
        
        IHand ownCurrentHand = PokerFactory.buildHand(holecards, board);
        
        List<Card> drawCandidates = new ArrayList<>(Card.values());
        drawCandidates.removeAll(boardCpy);
        drawCandidates.removeAll(holecards);
        
        List<EnumCandidate> holeCandidates = EnumCandidate.buildCandidates(holecards, board);
                
        for (EnumCandidate cand : holeCandidates) {
            //System.out.println(cand.toString());
            
            IHand oppCurentHand = PokerFactory.buildHand(cand.cards(), board);
            int curIdx = ownCurrentHand.compareTo(oppCurentHand) + 1;
            //HPTotal[curIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
            if (board.size() == 5) {
                HPTotal[curIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
            }
            else {
                for (Card drawCand : drawCandidates) {
                    
                    if (!cand.cards().contains(drawCand)) {
                        boardCpy.add(drawCand);
                        
                        IHand ownFutureHand = PokerFactory.buildHand(holecards, boardCpy);
                        IHand oppFutureHand = PokerFactory.buildHand(cand.cards(), boardCpy);
                        
                        int futIdx = ownFutureHand.compareTo(oppFutureHand) + 1;
                        //System.out.println("HandWeight: " + range.handWeight(cand.cards().get(0), cand.cards().get(1)));
                        //System.out.println("Cand weight: " + cand.weight());
                        
                        HP[curIdx][futIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
                        HPTotal[curIdx] += cand.weight() * range.handWeight(cand.cards().get(0), cand.cards().get(1));
                        
                        
                        boardCpy.remove(boardCpy.size() - 1);
                        //System.out.println("LOOP");
                    }
                }
            }
            
        }
        
        _numPl = numPlayers;
        calculatePotentials();
        _posPot *= POTENTIAL_COMP_RATIO;
        _negPot *= POTENTIAL_COMP_RATIO;
        
        //printData();
    }
    
} public interface IPlayerRange extends IHandRange {
    
    public boolean activePlayer();
    
} public class CompositeFieldRange implements IHandRange, IPlayerActionListener {
    
    private IWeightTable _wt;
    private List<IPlayerRange> _fieldRanges;
    
    public CompositeFieldRange(ITable table, IWeightTable wt) {
        _wt = wt;
        _fieldRanges = new ArrayList<>(10);
        table.registerListenAllPlayerEvents(this);
    }
    
    public void addRange(IPlayerRange range) {
        _fieldRanges.add(range);
        triggerReweight();
    }

    @Override
    public double handWeight(Card c1, Card c2) {
        return _wt.getWeight(c1, c2);
    }

    @Override
    public void gameAction(ITable table, IPlayerData plData, ActionType type, int amount) {
        if (type != ActionType.CALL || amount > 0) {
            triggerReweight();
        }
    }

    @Override
    public void leave(ITable table, IPlayerData plData) {
        
    }

    private void triggerReweight() {
        for (Card c1 : Card.values()) {
            for (Card c2 : Card.values()) {
                if (c1 != c2) {
                    IPlayerRange max = null;
                    for (IPlayerRange range : _fieldRanges) {
                        if (range.activePlayer()) {
                            if (max == null || max.handWeight(c1, c2) < range.handWeight(c1, c2)) {
                                max = range;
                            }
                        }
                    }
                    if (max != null) {
                        _wt.setWeight(c1, c2, max.handWeight(c1, c2));
                    }
                }
            }
        }
    }
} public interface IFrequencyTable {
    
    public double estimatedStrength(ActionType a, int numBets, Round bettingRound);
    
} public class PlayerWTRange implements IPlayerRange, IPlayerActionListener, IGameEventListener {

    private String _playerId;
    private IWeightTable _wt;
    private IFrequencyTable _freq;
    private IHandEvaluator _eval;
    private IHandRange _compositeRange;
    private static final double VARIANCE_FACTOR = 0.4;
    private static final double LOW_WEIGHT = 0.01;
    private static final double HIGH_WEIGHT = 1.00;
    private boolean _dead;
    private boolean _active;
    
    public PlayerWTRange(String playerId, IWeightTable wt, IFrequencyTable freq, IHandEvaluator eval, IHandRange compRange, ITable table) {
        _playerId = playerId;
        _wt = wt;
        _freq = freq;
        _eval = eval;
        _compositeRange = compRange;
        table.registerListenerForPlayer(playerId, this);
        table.registerEventListener(this);
    }
    
    private void reweight(double threshold, List<Card> board, int numPlayers, double potOdds) {
        double variance;
        
        variance = VARIANCE_FACTOR * (1 - threshold);
        
        List<EnumCandidate> holeCards = EnumCandidate.buildCandidates(null, board);
        
        _wt.initReweight();
        
        for (EnumCandidate holeCandidate : holeCards) {
            _eval.compute(holeCandidate.cards(), board, _compositeRange, numPlayers);
            
            if (board.size() > 0 && board.size() < 5 && _eval.posPotential() >= potOdds) {
                continue;
            }
            
            double reweight;
            
            if (threshold < variance) {
                double r = 1.0 - (2 * threshold / (threshold + variance));
                reweight = r + (_eval.effectiveHandStrength() / (threshold + variance));
            }
            else {
                reweight = (_eval.effectiveHandStrength() - threshold + variance) / (2 * variance);                
            }
            
            if (reweight < LOW_WEIGHT) {
                reweight = LOW_WEIGHT;
            }
            else if (reweight > HIGH_WEIGHT) {
                reweight = HIGH_WEIGHT;
            }
            
            double weight = _wt.getWeight(holeCandidate.cards().get(0), holeCandidate.cards().get(1));
            weight *= reweight;
            if (weight < LOW_WEIGHT) {
                weight = LOW_WEIGHT;
            }
            
            if (holeCandidate.generic()) {
                _wt.setWeight(holeCandidate.cards().get(0).rank(), holeCandidate.cards().get(1).rank(), weight);
            }
            else {
                _wt.suspendSetWeight(holeCandidate.cards().get(0), holeCandidate.cards().get(1), weight);
            }
        }
        _wt.consolidate();
    }
    
    private void triggerReweight(ITable table, ActionType a, double potOdds) {
        double threshold = _freq.estimatedStrength(a, table.numberBets(), table.round());
        
        reweight(threshold, table.board(), table.numberActivePlayers(), potOdds);
    }

    @Override
    public double handWeight(Card c1, Card c2) {
        return _wt.getWeight(c1, c2);
    }

    @Override
    public void gameAction(ITable table, IPlayerData plData, ActionType type, int amount) {
        if (type != ActionType.FOLD) {
            if (type != ActionType.CALL || amount > 0) {
                triggerReweight(table, type, plData.potOdds(table.potSize()));                
            }
        }
        else {
            _active = false;
        }
    }

    @Override
    public void leave(ITable table, IPlayerData plData) {
        //table.unregisterListenerForPlayer(_playerId, this);
        _dead = true;
        _active = false;
    }

    @Override
    public void roundBegin(ITable table, Round r) {
        if (!_dead) {
            _active = true;
        }
    }

    @Override
    public void roundEnd(ITable table, Round r) {
        
    }

    @Override
    public boolean activePlayer() {
        return _active;
    }

    @Override
    public void newHand(ITable table) {
        
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        _active = true;
        _dead = false;
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }

} public class GenericFrequencyTable implements IFrequencyTable {
    
    // Expert data used by University of Alberta's Poki implementation
    private static final double[][] _default = {
        {0.0, 0.5, 0.5},
        {0.5, 0.3, 0.2},
        {0.7, 0.2, 0.1}
    };
    
    @Override
    public double estimatedStrength(ActionType a, int numBets, Round bettingRound) {
        if (numBets > 2) {
            numBets = 2;
        }
        
        double ret = 1.0;
        for (int i = 2; i >= a.ordinal(); i--) {
            ret -= _default[numBets][i];
        }
        
        return ret;
    }
} public class PlayerSpecificFrequencyTable implements IFrequencyTable, IPlayerActionListener, IGameEventListener {
    
    // Dimensions: [BettingRound][LastAction][NumBets][Fold/Call/Bet]
    private int[][][][] _freqs;
    IFrequencyTable _defaultFreqs;
    private String _playerId;
    private static final int MINIMUM_DATA = 20;
    private ActionType _lastAction;
    
    public PlayerSpecificFrequencyTable(String playerId, ITable table) {
        _freqs = new int[4][4][3][3];
        _defaultFreqs = new GenericFrequencyTable();
        _playerId = playerId;
        table.registerPriorityListenerForPlayer(playerId, this);
        table.registerEventListener(this);
        //TODO persistence
    }

    @Override
    public double estimatedStrength(ActionType a, int numBets, Round bettingRound) {
        int total = 0;
        
        if (numBets > 2) {
            numBets = 2;
        }
        
        int lastIdx = ActionType.values().length;
        if (_lastAction != null) {
            lastIdx = _lastAction.ordinal();
        }
        
        for (int c : _freqs[bettingRound.ordinal()][lastIdx][numBets]) {
            total += c;
        }
        
        double actualFreq = 0.0;
        int count = total;

        for (int i = 2; i >= a.ordinal(); i--) {
            count -= _freqs[bettingRound.ordinal()][lastIdx][numBets][i];
        }
        
        if (total > 0) {
            actualFreq = (double) count / (double) total;
        }
        
        // Linearly interpolate with generic default frequency table if not enough data has been collected
        if (total < MINIMUM_DATA) {
            double defFreq = _defaultFreqs.estimatedStrength(a, numBets, bettingRound);
            actualFreq = (actualFreq * (total / ((double) MINIMUM_DATA)))
                    + (defFreq * ((MINIMUM_DATA - total) / ((double) MINIMUM_DATA)));
        }
        
        return actualFreq;
    }
    
    private void doIncrease(int numBets, Round r, ActionType a) {
        if (numBets > 2) {
            numBets = 2;
        }
        
        int lastIdx = ActionType.values().length;
        if (_lastAction != null) {
            lastIdx = _lastAction.ordinal();
        }
        
        _freqs[r.ordinal()][lastIdx][numBets][a.ordinal()]++;
        _lastAction = a;
    }

    @Override
    public void gameAction(ITable table, IPlayerData plData, ActionType type, int amount) {
        doIncrease(table.numberBets(), table.round(), type);
    }

    @Override
    public void leave(ITable table, IPlayerData plData) {
        //table.unregisterPriorityListenerForPlayer(_playerId, this);
    }

    @Override
    public void roundBegin(ITable table, Round r) {
        _lastAction = null;
    }

    @Override
    public void roundEnd(ITable table, Round r) {
        
    }

    @Override
    public void newHand(ITable table) {
        
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        _freqs = new int[4][4][3][3];
        for (int i = 0; i < _freqs.length; i++) {
            for (int j = 0; j < _freqs[i].length; j++) {
                for (int k = 0; k < _freqs[i][j].length; k++) {
                    for (int l = 0; l < _freqs[i][j][k].length; l++) {
                        _freqs[i][j][k][l] = 0;
                    }
                }
            }
        }
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }
    
} public class WeightTable implements IWeightTable, IGameEventListener {
    
    // Dimensions: [Rank1][Suit1][Rank2][Suit2][Weight]
    private double[][][][] _table1;
    private double[][][][] _table2;
    
    private double[][][][] _tableFrom;
    private double[][][][] _tableTo;
    
    List<WeightChange> _delayedChanges;
    
    public WeightTable(ITable table) {
        _table1 = new double[Rank.values().length][Suit.values().length][Rank.values().length][Suit.values().length];
        _table2 = new double[Rank.values().length][Suit.values().length][Rank.values().length][Suit.values().length];

        _delayedChanges = new ArrayList<>(100);
        
        table.registerEventListener(this);
    }
    
    private void setWeight(Rank r1, Suit s1, Rank r2, Suit s2, double weight) {
        _tableTo[r1.ordinal()][s1.ordinal()][r2.ordinal()][s2.ordinal()] = weight;
    }

    @Override
    public double getWeight(Card c1, Card c2) {
        return _tableFrom[c1.rank().ordinal()][c1.suit().ordinal()][c2.rank().ordinal()][c2.suit().ordinal()];
    }

    @Override
    public void suspendSetWeight(Card c1, Card c2, double weight) {
        _delayedChanges.add(new WeightChange(c1, c2, weight));
    }
    
    @Override
    public void setWeight(Card c1, Card c2, double weight) {
        setWeight(c1.rank(), c1.suit(), c2.rank(), c2.suit(), weight);
    }

    @Override
    public void setWeight(Rank r1, Rank r2, double weight) {
        for (Suit s1 : Suit.values()) {
            for (Suit s2 : Suit.values()) {
                if (r1 != r2 || s1 != s2) {
                    setWeight(r1, s1, r2, s2, weight);
                }
            }
        }
    }

    @Override
    public void initReweight() {
        _tableFrom = _table1;
        _tableTo = _table2;
    }

    @Override
    public void consolidate() {
        _tableFrom = _table2;
        
        for (WeightChange change : _delayedChanges) {
            setWeight(change.card1(), change.card2(), change.newWeight());
        }
        
        _delayedChanges.clear();
    }

    @Override
    public void roundBegin(ITable table, Round r) {
        if (r == Round.PREFLOP) {
            for (double[][][] arr1 : _table1) {
                for (double[][] arr2 : arr1) {
                    for (double[] arr3 : arr2) {
                        for (int i = 0; i < arr3.length; i++) {
                            arr3[i] = 1.0;
                        }
                    }
                }
            }
        }

        _tableFrom = _table1;
        _tableTo = _table2;
    }
    
    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        double[][][][] tmp = _table1;
        _table1 = _table2;
        _table2 = tmp;
    }

    @Override
    public void newHand(ITable table) {
        
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        
    }
    
} public class PokerTable implements ITable {
    
    private static final int MAXBETS_ROUND = 4;
    
    private int _numberHandsInGame;
    private int _numberActivePlayers;
    private int _numberTotalPlayers;
    private List<String> _playerIds;
    private Round _currentRound;
    private int _numberBets;
    private int _numberCallers;
    private int _amountCall;
    private int _pot;
    
    private int _cardsDrawn;
    
    private IActionBuilder _actionBuild;
    
    private int _sBlind;
    private int _bBlind;
    
    private IDeck _deck;
    private List<Card> _board;
    
    private LinkedList<PlayerData> _players;
    private LinkedList<PlayerData> _playersToAct;
    private LinkedList<PlayerData> _playersActed;
    private LinkedList<PlayerData> _deadPlayers;
    
    private List<IGameEventListener> _roundListeners;
    private List<IPlayerActionListener> _allEventsListeners;
    
    // Dimension: [BettingRound]
    private int[] _cardsToDraw = {0, 3, 1, 1};
    
    public PokerTable(IActionBuilder actionBuild) {
        _actionBuild = actionBuild;
        
        _numberTotalPlayers = 0;
        _players = new LinkedList<>();
        _playersToAct = new LinkedList<>();
        _playersActed = new LinkedList<>();
        _deadPlayers = new LinkedList<>();
        _playerIds = new ArrayList<>(10);
        
        _deck = PokerFactory.buildDeck();
        _board = new ArrayList<>(5);
        
        _roundListeners = new ArrayList<>(25);
        _allEventsListeners = new ArrayList<>(25);
    }
    
    public void addPlayer(IPlayer pl) {
        _players.add(new PlayerData(pl, this));
        _numberTotalPlayers++;
        _playerIds.add(pl.id());
    }
    
    @Override
    public void runGame(int hands, int stackSize, int sBlind, int bBlind) {
        _sBlind = sBlind;
        _bBlind = bBlind;
        _numberHandsInGame = 0;
        _players.addAll(_deadPlayers);
        _deadPlayers.clear();
        for (IGameEventListener listener : _roundListeners) {
            listener.newGame(this, stackSize, _sBlind, _bBlind, _playerIds);
        }
        while (_players.size() > 1 && _numberHandsInGame < hands) {
            _numberHandsInGame++;

            _pot = 0;
            _amountCall = _bBlind;
            _currentRound = null;
            boolean sb = false;
            boolean bb = false;
            boolean blinds = false;
            
            _board.clear();
            _deck.shuffle();
            
            for (IGameEventListener listener : _roundListeners) {
                listener.newHand(this);
            }
            
            _deck.burnCard();
            for (int i = 0; i < 2; i++) {
                for (PlayerData pl : _players) {
                    pl.player().dealCard(_deck.drawCard());
                }
            }
            
            int pos = 1;
            for (PlayerData pl : _players) {
                pl.setPosition(pos++);
            }
            
            _numberActivePlayers = _players.size();
            _playersToAct.clear();
            _playersActed.clear();
            _playersToAct.addAll(_players);
            _numberActivePlayers = _playersToAct.size();
                
            while (_currentRound != Round.RIVER && _numberActivePlayers > 1) {
                int cardsDrawn2 = _cardsDrawn;
                if (_currentRound == null) {
                    _currentRound = Round.PREFLOP;
                }
                else {
                    _currentRound = Round.values()[_currentRound.ordinal() + 1];
                }
                
                _numberCallers = 0;
                _numberBets = 0;
                _amountCall = 0;
                
                if (_cardsToDraw[_currentRound.ordinal()] > 0) {
                    _deck.burnCard();
                    for (int i = _cardsToDraw[_currentRound.ordinal()]; i > 0; i--) {
                        _board.add(_deck.drawCard());
                    }                    
                }
                
                for (IGameEventListener listener : _roundListeners) {
                    listener.roundBegin(this, _currentRound);
                }
                
                while (_playersToAct.size() > 0) {
                    boolean raised = false;
                    
                    Iterator<PlayerData> activePlayers = _playersToAct.iterator();
                    PlayerData pl = null;
                    
                    while (activePlayers.hasNext()) {
                 
                        pl = activePlayers.next();
                        activePlayers.remove();
                        
                        if (pl.allIn() || _numberActivePlayers == 1) {
                            _playersActed.add(pl);
                            continue;
                        }
                        
                        blinds = false;
                        _actionBuild.setContext(this, pl);
                        IAction act;
                        if (!sb || !bb) {
                            blinds = true;
                            if (!sb) {
                                act = _actionBuild.makeSBlind();
                                sb = true;
                            }
                            else {
                                act = _actionBuild.makeBBlind();
                                bb = true;
                            }
                        }
                        else {
                            act = pl.player().action(this, pl, _actionBuild);   
                        }
                        
                        if (act == null || act.type() == ActionType.FOLD) {
                            _numberActivePlayers--;
                            broadcastGameAction(pl, ActionType.FOLD, 0, pl.prioListeners());
                            broadcastGameAction(pl, ActionType.FOLD, 0, pl.listeners());
                            broadcastGameAction(pl, ActionType.FOLD, 0, _allEventsListeners);
                        }
                        else {
                            if (act.type() == ActionType.CALL) {
                                _playersActed.add(pl);
                                playerCall(pl, act.amount());
                            }
                            if (act.type() == ActionType.BET) {
                                if (_numberBets < MAXBETS_ROUND) {
                                    playerBet(pl, act.amount(), blinds);
                                    raised = true;                                    
                                }
                                else {
                                    _playersActed.add(pl);
                                    playerCall(pl, act.amount());
                                }
                            }
                            broadcastGameAction(pl, act.type(), act.amount(), pl.prioListeners());
                            broadcastGameAction(pl, act.type(), act.amount(), pl.listeners());
                            broadcastGameAction(pl, act.type(), act.amount(), _allEventsListeners);
                            if (raised) {
                                break;
                            }
                         }
                        
                    }
                    if (raised) {
                        _playersToAct.addAll(_playersActed);
                        _playersActed.clear();
                        _numberActivePlayers = _playersToAct.size() + 1;
                        _numberCallers = 0;
                        if (blinds) {
                            _playersToAct.add(pl);
                        }
                        else {
                            _playersActed.add(pl);                            
                        }
                    }
                }
                
                if (_currentRound == Round.RIVER || _playersActed.size() == 1) {
                    while (_pot > 0) {
                        
                        PlayerData winner = null;
                        List<PlayerData> _splitters = new ArrayList<>(_playersActed.size() + 1);
                        int winnerIdx = 0;
                        
                        if (_playersActed.size() > 1) {
                            
                            IHand winHand = null;
                            Iterator<PlayerData> it = _playersActed.iterator();
                            int i = 0;
                            
                            while (it.hasNext()) {
                                PlayerData pl = it.next();
                                IHand oHand = PokerFactory.buildHand(pl.player().holeCards(), _board);
                                int cmp;
                                
                                if (winHand == null || (cmp = winHand.compareTo(oHand)) < 0) {
                                    winner = pl;
                                    winHand = oHand;
                                    _splitters.clear();
                                    winnerIdx = i;
                                }
                                else if (cmp == 0) {
                                    _splitters.add(pl);
                                }
                                i++;
                            }
                        }
                        else if (_playersActed.size() == 1) {
                            winner = _playersActed.get(0);
                        }
                        else {
                            // Discarding odd chip left (odd split amount etc.)
                            _pot = 0;
                            break;
                        }
                        
                        int winnings = _pot / (_splitters.size() + 1);
                        int didWin = winner.winPot(winnings);
                        _pot -= didWin;
                        for (IGameEventListener listener : _roundListeners) {
                            listener.winHand(this, winner, didWin);
                        }
                        for (PlayerData split : _splitters) {
                            didWin = split.winPot(winnings);
                            _pot -= didWin;
                            for (IGameEventListener listener : _roundListeners) {
                                listener.winHand(this, winner, didWin);
                            }
                        }
                        _playersActed.remove(winner);
                        _playersActed.removeAll(_splitters);
                    }
                }
                else {
                    _playersToAct.addAll(_players);
                    _playersToAct.retainAll(_playersActed);
                    _playersActed.clear();
                }
                
                for (IGameEventListener listener : _roundListeners) {
                    listener.roundEnd(this, _currentRound);
                }
            }
                        
            if (_players.size() > 1) {
                PlayerData dealer = _players.removeFirst();
                _players.addLast(dealer);
            }
            
            Iterator<PlayerData> plIt = _players.iterator();
            while (plIt.hasNext()) {
                PlayerData pl = plIt.next();
                
                if (pl.stack() <= 0) {
                    plIt.remove();
                    for (IPlayerActionListener listener : pl.prioListeners()) {
                        listener.leave(this, pl);
                    }
                    for (IPlayerActionListener listener : pl.listeners()) {
                        listener.leave(this, pl);
                    }
                    _deadPlayers.add(pl);
                }
            }

        }
    }
    
    private void broadcastGameAction(IPlayerData plData, ActionType type, int amount, List<IPlayerActionListener> listeners) {
        for (IPlayerActionListener listener : listeners) {
            listener.gameAction(this, plData, type, amount);
        }
    }

    private void playerBet(PlayerData pl, int amount, boolean blind) {
        if (pl.amountToCall() > 0) {
            playerCall(pl, pl.amountToCall());
        }
        _pot += pl.placeMoney(amount);
        if (!blind) {
            _numberBets++;            
        }
        _amountCall += amount;
        _numberCallers = 0;
        playerUpdateMaxWinnings(pl, amount);
        for (PlayerData o : _players) {
            if (pl != o) {
                o.betAgainst(amount, blind);
            }
        }
    }
    
    private void playerCall(PlayerData pl, int amount) {
        _pot += pl.placeMoney(pl.amountToCall());
        _numberCallers++;
        playerUpdateMaxWinnings(pl, amount);
    }
    
    private void playerUpdateMaxWinnings(PlayerData pl, int amount) {
        for (PlayerData o : _playersActed) {
            if (pl != o) {
                o.updateMaxWin(pl, amount);                
            }
        }
        for (PlayerData o : _playersToAct) {
            if (pl != o) {
                o.updateMaxWin(pl, amount);                
            }
        }
    }

    @Override
    public int numberBets() {
        return _numberBets;
    }
    
    @Override
    public int maxBets() {
        return MAXBETS_ROUND;
    }
    
    @Override
    public int numberCallers() {
        return _numberCallers;
    }

    @Override
    public Round round() {
        return _currentRound;
    }
    
    @Override
    public int potSize() {
        return _pot;
    }

    @Override
    public int numberActivePlayers() {
        return _numberActivePlayers;
    }
    
    @Override
    public int numberPlayersToAct() {
        return _playersToAct.size();
    }
    
    @Override
    public int numberPlayers() {
        return _players.size();
    }

    @Override
    public List<Card> board() {
        return _board;
    }
    
    @Override
    public int smallBlind() {
        return _sBlind;
    }
    
    @Override
    public int bigBlind() {
        return _bBlind;
    }

    @Override
    public void registerListenerForPlayer(String id, IPlayerActionListener listener) {
        for (PlayerData pl : _players) {
            if (pl.player().id().equals(id)) {
                pl.listeners().add(listener);
                break;
            }
        }
    }

    @Override
    public void registerPriorityListenerForPlayer(String id, IPlayerActionListener listener) {
        for (PlayerData pl : _players) {
            if (pl.player().id().equals(id)) {
                pl.prioListeners().add(listener);
                break;
            }
        }
    }

    @Override
    public void registerEventListener(IGameEventListener listener) {
        _roundListeners.add(listener);
    }

    @Override
    public void registerListenAllPlayerEvents(IPlayerActionListener listener) {
        _allEventsListeners.add(listener);
    }

    @Override
    public String playerSmallBlind() {
        if (_players.size() > 0) {
            return _players.get(0).id();
        }
        return null;
    }

    @Override
    public String playerBigBlind() {
        if (_players.size() > 1) {
            return _players.get(1).id();
        }
        return null;
    }

    @Override
    public String playerDealer() {
        if (_players.size() > 1) {
            if (_players.size() > 2) {
                return _players.getLast().id();
            }
            else {
                return _players.getFirst().id();
            }
        }
        return null;
    }
    
} public class LimitRules extends AActionBuilder {

    @Override
    public int maxRaise() {
        if (_plStack < _bBlind) {
            return _plStack;
        }
        return _bBlind;
    }
} public class HumanPlayer implements IPlayer, IGameEventListener, IPlayerActionListener {
    
    private String _id;
    private List<Card> _holeCards;
    private int _stack;
    
    public HumanPlayer(ITable table) {
        System.out.println("Enter name: ");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            _id = in.readLine();
        } catch (IOException ex) {
            _id = "Player" + new Random().nextInt();
        }
        _holeCards = new ArrayList<>(2);
        table.registerEventListener(this);
        table.registerListenAllPlayerEvents(this);
    }

    @Override
    public String id() {
        return _id;
    }

    @Override
    public void dealCard(Card c) {
        _holeCards.add(c);
    }
    
    @Override
    public List<Card> holeCards() {
        return _holeCards;
    }

    @Override
    public IAction action(ITable table, IPlayerData plData, IActionBuilder plContext) {
        System.out.println("##### " + _id + " ACTS #####");
        //System.out.println("--------------------");

        //System.out.println("--------------------");
        System.out.print("Hand: ");
        for (Card c : _holeCards) {
            System.out.print(" [" + c.toString() + "] ");
        }
        System.out.println();
        System.out.println("STACK: " + plData.stack());
        System.out.println("POT: " + table.potSize());
        System.out.println("TO CALL: " + plData.betsToCall() + " bets [" + plData.amountToCall() + "]");
        System.out.println("Input selection:");
        System.out.println("[1] Fold");
        System.out.println("[2] Check/Call");
        System.out.println("[3] Bet");
        Scanner in = new Scanner(System.in);
        while (true) {
            int res = in.nextInt();
            switch (res) {
                case 1: return plContext.makeFold();
                case 2: return plContext.makeCall();
                case 3: return plContext.makeRaise(plContext.minRaise());
                default: System.out.println("Bad input.");
            }
        }
    }

    @Override
    public void roundBegin(ITable table, ITable.Round r) {
        System.out.println("%%% START OF BETTING ROUND: " + r.toString());
        System.out.print("Board: ");
        for (Card c : table.board()) {
            System.out.print(" [" + c.toString() + "] ");
        }
        System.out.println();
    }

    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        System.out.println("%%%");
    }

    @Override
    public void newHand(ITable table) {
        System.out.println("********************* NEW HAND *********************");
        System.out.println("DEALER: " + table.playerDealer());
        System.out.println("SMALL BLIND: " + table.playerSmallBlind());
        System.out.println("BIG BLIND: " + table.playerBigBlind());
        _holeCards.clear();
    }

    @Override
    public void gameAction(ITable table, IPlayerData plData, ITable.ActionType type, int amount) {
        System.out.print("Player [" + plData.id() + "] action: " + type.toString());
        if (amount > 0) {
            System.out.print(" [" + amount + "]");
            if (plData.stack() <= amount) {
                System.out.print(" (ALL IN)");
            }
        }
        System.out.println();
    }

    @Override
    public void leave(ITable table, IPlayerData plData) {
        System.out.println("Player [" + plData.id() + "] left the table");
    }

    @Override
    public void newGame(ITable table, int stackSize, int sBlind, int bBlind, List<String> players) {
        System.out.println("### GAME START ###");
        System.out.println("Stack size: " + stackSize);
        System.out.println("Small blind: " + sBlind);
        System.out.println("Big blind: " + bBlind);
        System.out.println("Num players: " + players.size());
    }

    @Override
    public void winHand(ITable table, IPlayerData data, int amount) {
        System.out.println("[WIN] Player " + data.id() + " wins " + amount);
    }

    @Override
    public void setStack(int stack) {
        _stack = stack;
    }

    @Override
    public int stack() {
        return _stack;
    }
    
} public class HumanPlayerGUI extends javax.swing.JFrame implements IPlayer, IGameEventListener, IPlayerActionListener {
    
        
    private String _id;
    private final static String _iconPath = "img/";
    private ImageIcon[][] _cardImgs;
    
    private List<JLabel> boardCards;
    private List<JLabel> plIds;
    private List<JLabel> plAmts;
    private List<JLabel> plSts;
    
    private List<Card> _holeCards;
    private int _stack;
    
    private List<String> _players;
    
    private IActionBuilder _actionBuild;
    private IAction _chosenAction;

    /**
     * Creates new form TestGUI
     */
    public HumanPlayerGUI(ITable table) {
        initComponents();
        
        askId();
        _cardImgs = new ImageIcon[Card.Suit.values().length][Card.Rank.values().length];
        for (Card.Suit s : Card.Suit.values()) {
            for (Card.Rank r : Card.Rank.values()) {
                _cardImgs[s.ordinal()][r.ordinal()] = loadCard(r, s);
                if (_cardImgs[s.ordinal()][r.ordinal()] == null) {
                    System.out.println("got null card");
                }
            }
        }
        
        _holeCards = new ArrayList<>(2);        
        boardCards = new ArrayList<>(5);
        plIds = new ArrayList<>(8);
        plAmts = new ArrayList<>(8);
        plSts = new ArrayList<>(8);
        
        plIds.add(P1Name);
        plIds.add(P2Name);
        plIds.add(P3Name);
        plIds.add(P4Name);
        plIds.add(P5Name);
        plIds.add(P6Name);
        plIds.add(P7Name);
        plIds.add(P8Name);
        
        plAmts.add(P1Amt);
        plAmts.add(P2Amt);
        plAmts.add(P3Amt);
        plAmts.add(P4Amt);
        plAmts.add(P5Amt);
        plAmts.add(P6Amt);
        plAmts.add(P7Amt);
        plAmts.add(P8Amt);
        
        plSts.add(P1St);
        plSts.add(P2St);
        plSts.add(P3St);
        plSts.add(P4St);
        plSts.add(P5St);
        plSts.add(P6St);
        plSts.add(P7St);
        plSts.add(P8St);
        
        boardCards.add(boardC1);
        boardCards.add(boardC2);
        boardCards.add(boardC3);
        boardCards.add(boardC4);
        boardCards.add(boardC5);
        
        table.registerEventListener(this);
        table.registerListenAllPlayerEvents(this);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                setVisible(true);
                setResizable(false);
                setTitle("Lucki Poker: " + _id);
            } 
        });
    }
    
    private ImageIcon loadCard(Card.Rank rank, Card.Suit suit) {
        String path = _iconPath + rank.toString() + "_" + suit.toString() + "2.png";
        
        ImageIcon ret = new ImageIcon(path);
        
        System.out.println("Loaded " + rank.toString() + " of " + suit.toString() + " h:" + ret.getIconHeight() + " w:" + ret.getIconWidth());
        
        return ret;
    }
    
    private void askId() {
        while (_id == null) {
            String id = (String) JOptionPane.showInputDialog("Name?");
            if (id != null && id.length() >= 3) {
                _id = id;
            }
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jPanel1 = new javax.swing.JPanel();
        PanelP2 = new javax.swing.JPanel();
        P2Name = new javax.swing.JLabel();
        P2Amt = new javax.swing.JLabel();
        P2St = new javax.swing.JLabel();
        PanelP3 = new javax.swing.JPanel();
        P3Name = new javax.swing.JLabel();
        P3Amt = new javax.swing.JLabel();
        P3St = new javax.swing.JLabel();
        PanelP7 = new javax.swing.JPanel();
        P7Name = new javax.swing.JLabel();
        P7Amt = new javax.swing.JLabel();
        P7St = new javax.swing.JLabel();
        PanelP6 = new javax.swing.JPanel();
        P6Name = new javax.swing.JLabel();
        P6Amt = new javax.swing.JLabel();
        P6St = new javax.swing.JLabel();
        PanelP5 = new javax.swing.JPanel();
        P5Name = new javax.swing.JLabel();
        P5Amt = new javax.swing.JLabel();
        P5St = new javax.swing.JLabel();
        PanelP1 = new javax.swing.JPanel();
        P1Name = new javax.swing.JLabel();
        P1Amt = new javax.swing.JLabel();
        P1St = new javax.swing.JLabel();
        jPanel8 = new javax.swing.JPanel();
        jPanel9 = new javax.swing.JPanel();
        PanelP4 = new javax.swing.JPanel();
        P4Name = new javax.swing.JLabel();
        P4Amt = new javax.swing.JLabel();
        P4St = new javax.swing.JLabel();
        PanelP8 = new javax.swing.JPanel();
        P8Name = new javax.swing.JLabel();
        P8Amt = new javax.swing.JLabel();
        P8St = new javax.swing.JLabel();
        PanelBoard = new javax.swing.JPanel();
        boardC1 = new javax.swing.JLabel();
        boardC2 = new javax.swing.JLabel();
        boardC3 = new javax.swing.JLabel();
        boardC4 = new javax.swing.JLabel();
        boardC5 = new javax.swing.JLabel();
        jPanel2 = new javax.swing.JPanel();
        IdLabel = new javax.swing.JLabel();
        StackLabel = new javax.swing.JLabel();
        holeC1 = new javax.swing.JLabel();
        holeC2 = new javax.swing.JLabel();
        potLabel = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        logArea = new javax.swing.JTextArea();
        jPanel3 = new javax.swing.JPanel();
        foldBtn = new javax.swing.JButton();
        callBtn = new javax.swing.JButton();
        betBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jPanel1.setLayout(new java.awt.GridBagLayout());

        PanelP2.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P2Name.setText("Player2");

        P2Amt.setText("P2AMT");

        P2St.setText("P2ST");

        javax.swing.GroupLayout PanelP2Layout = new javax.swing.GroupLayout(PanelP2);
        PanelP2.setLayout(PanelP2Layout);
        PanelP2Layout.setHorizontalGroup(
            PanelP2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P2Name)
                    .addComponent(P2Amt)
                    .addComponent(P2St))
                .addContainerGap(43, Short.MAX_VALUE))
        );
        PanelP2Layout.setVerticalGroup(
            PanelP2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP2Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P2Name)
                .addGap(18, 18, 18)
                .addComponent(P2Amt)
                .addGap(18, 18, 18)
                .addComponent(P2St)
                .addContainerGap(16, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP2, gridBagConstraints);

        PanelP3.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P3Name.setText("Player3");

        P3Amt.setText("P3AMT");

        P3St.setText("P3ST");

        javax.swing.GroupLayout PanelP3Layout = new javax.swing.GroupLayout(PanelP3);
        PanelP3.setLayout(PanelP3Layout);
        PanelP3Layout.setHorizontalGroup(
            PanelP3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP3Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P3St)
                    .addComponent(P3Name)
                    .addComponent(P3Amt))
                .addContainerGap(57, Short.MAX_VALUE))
        );
        PanelP3Layout.setVerticalGroup(
            PanelP3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP3Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P3Name)
                .addGap(18, 18, 18)
                .addComponent(P3Amt)
                .addGap(18, 18, 18)
                .addComponent(P3St)
                .addContainerGap(16, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP3, gridBagConstraints);

        PanelP7.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P7Name.setText("Player7");

        P7Amt.setText("P7AMT");

        P7St.setText("P7ST");

        javax.swing.GroupLayout PanelP7Layout = new javax.swing.GroupLayout(PanelP7);
        PanelP7.setLayout(PanelP7Layout);
        PanelP7Layout.setHorizontalGroup(
            PanelP7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP7Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P7Name)
                    .addComponent(P7Amt)
                    .addComponent(P7St))
                .addContainerGap(29, Short.MAX_VALUE))
        );
        PanelP7Layout.setVerticalGroup(
            PanelP7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP7Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P7Name)
                .addGap(18, 18, 18)
                .addComponent(P7Amt)
                .addGap(18, 18, 18)
                .addComponent(P7St)
                .addContainerGap(20, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP7, gridBagConstraints);

        PanelP6.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P6Name.setText("Player6");

        P6Amt.setText("P6AMT");

        P6St.setText("P6ST");

        javax.swing.GroupLayout PanelP6Layout = new javax.swing.GroupLayout(PanelP6);
        PanelP6.setLayout(PanelP6Layout);
        PanelP6Layout.setHorizontalGroup(
            PanelP6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP6Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P6Name)
                    .addComponent(P6Amt)
                    .addComponent(P6St))
                .addContainerGap(43, Short.MAX_VALUE))
        );
        PanelP6Layout.setVerticalGroup(
            PanelP6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP6Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P6Name)
                .addGap(18, 18, 18)
                .addComponent(P6Amt)
                .addGap(18, 18, 18)
                .addComponent(P6St)
                .addContainerGap(20, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP6, gridBagConstraints);

        PanelP5.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P5Name.setText("Player5");

        P5Amt.setText("P5AMT");

        P5St.setText("P5ST");

        javax.swing.GroupLayout PanelP5Layout = new javax.swing.GroupLayout(PanelP5);
        PanelP5.setLayout(PanelP5Layout);
        PanelP5Layout.setHorizontalGroup(
            PanelP5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP5Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P5Name)
                    .addComponent(P5Amt)
                    .addComponent(P5St))
                .addContainerGap(57, Short.MAX_VALUE))
        );
        PanelP5Layout.setVerticalGroup(
            PanelP5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP5Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P5Name)
                .addGap(18, 18, 18)
                .addComponent(P5Amt)
                .addGap(18, 18, 18)
                .addComponent(P5St)
                .addContainerGap(20, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP5, gridBagConstraints);

        PanelP1.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P1Name.setText("Player1");

        P1Amt.setText("P1AMT");

        P1St.setText("P1ST");

        javax.swing.GroupLayout PanelP1Layout = new javax.swing.GroupLayout(PanelP1);
        PanelP1.setLayout(PanelP1Layout);
        PanelP1Layout.setHorizontalGroup(
            PanelP1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P1St)
                    .addComponent(P1Name)
                    .addComponent(P1Amt))
                .addContainerGap(29, Short.MAX_VALUE))
        );
        PanelP1Layout.setVerticalGroup(
            PanelP1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP1Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P1Name)
                .addGap(18, 18, 18)
                .addComponent(P1Amt)
                .addGap(18, 18, 18)
                .addComponent(P1St)
                .addContainerGap(16, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP1, gridBagConstraints);

        javax.swing.GroupLayout jPanel8Layout = new javax.swing.GroupLayout(jPanel8);
        jPanel8.setLayout(jPanel8Layout);
        jPanel8Layout.setHorizontalGroup(
            jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 100, Short.MAX_VALUE)
        );
        jPanel8Layout.setVerticalGroup(
            jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 100, Short.MAX_VALUE)
        );

        jPanel1.add(jPanel8, new java.awt.GridBagConstraints());

        javax.swing.GroupLayout jPanel9Layout = new javax.swing.GroupLayout(jPanel9);
        jPanel9.setLayout(jPanel9Layout);
        jPanel9Layout.setHorizontalGroup(
            jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 100, Short.MAX_VALUE)
        );
        jPanel9Layout.setVerticalGroup(
            jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 100, Short.MAX_VALUE)
        );

        jPanel1.add(jPanel9, new java.awt.GridBagConstraints());

        PanelP4.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P4Name.setText("Player4");

        P4Amt.setText("P4AMT");

        P4St.setText("P4ST");

        javax.swing.GroupLayout PanelP4Layout = new javax.swing.GroupLayout(PanelP4);
        PanelP4.setLayout(PanelP4Layout);
        PanelP4Layout.setHorizontalGroup(
            PanelP4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP4Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P4Name)
                    .addComponent(P4Amt)
                    .addComponent(P4St))
                .addContainerGap(25, Short.MAX_VALUE))
        );
        PanelP4Layout.setVerticalGroup(
            PanelP4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP4Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P4Name)
                .addGap(18, 18, 18)
                .addComponent(P4Amt)
                .addGap(18, 18, 18)
                .addComponent(P4St)
                .addContainerGap(18, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP4, gridBagConstraints);

        PanelP8.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        P8Name.setText("Player8");

        P8Amt.setText("P8AMT");

        P8St.setText("P8ST");

        javax.swing.GroupLayout PanelP8Layout = new javax.swing.GroupLayout(PanelP8);
        PanelP8.setLayout(PanelP8Layout);
        PanelP8Layout.setHorizontalGroup(
            PanelP8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP8Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelP8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(P8St)
                    .addComponent(P8Name)
                    .addComponent(P8Amt))
                .addContainerGap(25, Short.MAX_VALUE))
        );
        PanelP8Layout.setVerticalGroup(
            PanelP8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelP8Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(P8Name)
                .addGap(18, 18, 18)
                .addComponent(P8Amt)
                .addGap(18, 18, 18)
                .addComponent(P8St)
                .addContainerGap(18, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelP8, gridBagConstraints);

        PanelBoard.setBackground(new java.awt.Color(64, 128, 64));
        PanelBoard.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0), 1, true));

        boardC1.setText("Card1");

        boardC2.setText("Card2");

        boardC3.setText("Card3");

        boardC4.setText("Card4");

        boardC5.setText("Card5");

        javax.swing.GroupLayout PanelBoardLayout = new javax.swing.GroupLayout(PanelBoard);
        PanelBoard.setLayout(PanelBoardLayout);
        PanelBoardLayout.setHorizontalGroup(
            PanelBoardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelBoardLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(boardC1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(boardC2)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(boardC3)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(boardC4)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(boardC5)
                .addContainerGap(93, Short.MAX_VALUE))
        );
        PanelBoardLayout.setVerticalGroup(
            PanelBoardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(PanelBoardLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(PanelBoardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(boardC1)
                    .addComponent(boardC2)
                    .addComponent(boardC3)
                    .addComponent(boardC4)
                    .addComponent(boardC5))
                .addContainerGap(86, Short.MAX_VALUE))
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        jPanel1.add(PanelBoard, gridBagConstraints);

        jPanel2.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));

        IdLabel.setText("Id");

        StackLabel.setText("Stack");

        holeC1.setText("Card1");

        holeC2.setText("Card2");

        potLabel.setText("Pot");

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(StackLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(potLabel))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(IdLabel)
                            .addGroup(jPanel2Layout.createSequentialGroup()
                                .addComponent(holeC1)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(holeC2)))
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(IdLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(StackLabel)
                    .addComponent(potLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(holeC1)
                    .addComponent(holeC2))
                .addContainerGap(71, Short.MAX_VALUE))
        );

        logArea.setColumns(20);
        logArea.setRows(5);
        jScrollPane1.setViewportView(logArea);

        foldBtn.setText("Fold");
        foldBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                foldBtnActionPerformed(evt);
            }
        });

        callBtn.setText("Check");
        callBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                callBtnActionPerformed(evt);
            }
        });

        betBtn.setText("Bet");
        betBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                betBtnActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
        jPanel3.setLayout(jPanel3Layout);
        jPanel3Layout.setHorizontalGroup(
            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel3Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(foldBtn)
                .addGap(18, 18, 18)
                .addComponent(callBtn)
                .addGap(18, 18, 18)
                .addComponent(betBtn)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        jPanel3Layout.setVerticalGroup(
            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel3Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(foldBtn)
                    .addComponent(callBtn)
                    .addComponent(betBtn))
                .addContainerGap(39, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jScrollPane1))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 603, Short.MAX_VALUE)
                    .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane1)))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void betBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_betBtnActionPerformed
        _chosenAction = _actionBuild.makeRaise(_actionBuild.minRaise());
    }//GEN-LAST:event_betBtnActionPerformed

    private void callBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_callBtnActionPerformed
        _chosenAction = _actionBuild.makeCall();
    }//GEN-LAST:event_callBtnActionPerformed

    private void foldBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_foldBtnActionPerformed
        _chosenAction = _actionBuild.makeFold();
    }//GEN-LAST:event_foldBtnActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel IdLabel;
    private javax.swing.JLabel P1Amt;
    private javax.swing.JLabel P1Name;
    private javax.swing.JLabel P1St;
    private javax.swing.JLabel P2Amt;
    private javax.swing.JLabel P2Name;
    private javax.swing.JLabel P2St;
    private javax.swing.JLabel P3Amt;
    private javax.swing.JLabel P3Name;
    private javax.swing.JLabel P3St;
    private javax.swing.JLabel P4Amt;
    private javax.swing.JLabel P4Name;
    private javax.swing.JLabel P4St;
    private javax.swing.JLabel P5Amt;
    private javax.swing.JLabel P5Name;
    private javax.swing.JLabel P5St;
    private javax.swing.JLabel P6Amt;
    private javax.swing.JLabel P6Name;
    private javax.swing.JLabel P6St;
    private javax.swing.JLabel P7Amt;
    private javax.swing.JLabel P7Name;
    private javax.swing.JLabel P7St;
    private javax.swing.JLabel P8Amt;
    private javax.swing.JLabel P8Name;
    private javax.swing.JLabel P8St;
    private javax.swing.JPanel PanelBoard;
    private javax.swing.JPanel PanelP1;
    private javax.swing.JPanel PanelP2;
    private javax.swing.JPanel PanelP3;
    private javax.swing.JPanel PanelP4;
    private javax.swing.JPanel PanelP5;
    private javax.swing.JPanel PanelP6;
    private javax.swing.JPanel PanelP7;
    private javax.swing.JPanel PanelP8;
    private javax.swing.JLabel StackLabel;
    private javax.swing.JButton betBtn;
    private javax.swing.JLabel boardC1;
    private javax.swing.JLabel boardC2;
    private javax.swing.JLabel boardC3;
    private javax.swing.JLabel boardC4;
    private javax.swing.JLabel boardC5;
    private javax.swing.JButton callBtn;
    private javax.swing.JButton foldBtn;
    private javax.swing.JLabel holeC1;
    private javax.swing.JLabel holeC2;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel8;
    private javax.swing.JPanel jPanel9;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea logArea;
    private javax.swing.JLabel potLabel;
    // End of variables declaration//GEN-END:variables

    @Override
    public String id() {
        return _id;
    }

    @Override
    public void dealCard(Card c) {
        _holeCards.add(c);
    }

    @Override
    public List<Card> holeCards() {
        return _holeCards;
    }

    @Override
    public IAction action(final ITable table, final IPlayerData plData, final IActionBuilder actionBuild) {
        final JFrame f = this;
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    _actionBuild = actionBuild;
                    
                    String opt2;
                    if (plData.amountToCall() == 0) {
                        opt2 = "Check";
                    }
                    else {
                        opt2 = "Call";
                    }
                    
                    callBtn.setText(opt2);
                    
                    foldBtn.setVisible(true);
                    callBtn.setVisible(true);
                    if (table.numberBets() < table.maxBets()) {
                        betBtn.setVisible(true);                        
                    }
                }
            });
        } catch (Exception e) {
            
        }

        
        while (_chosenAction == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                
            }
        }
                
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                foldBtn.setVisible(false);
                callBtn.setVisible(false);
                betBtn.setVisible(false);
            }
        });
        
        IAction ret = _chosenAction;
        _chosenAction = null;
        _actionBuild = null;
        
        return ret;
    }

    @Override
    public void setStack(int stack) {
        _stack = stack;
        StackLabel.setText("Stack: " + stack);
    }

    @Override
    public int stack() {
        return _stack;
    }

    @Override
    public void roundBegin(final ITable table, final ITable.Round r) {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    for (JLabel amt : plAmts) {
                        amt.setText("");
                    }
                    holeC1.setIcon(_cardImgs[_holeCards.get(0).suit().ordinal()][holeCards().get(0).rank().ordinal()]);
                    holeC2.setIcon(_cardImgs[_holeCards.get(1).suit().ordinal()][holeCards().get(1).rank().ordinal()]);
                    holeC1.setText("");
                    holeC2.setText("");
                    holeC1.setVisible(true);
                    holeC2.setVisible(true);
                    
                    int i = 0;
                    for (Card c : table.board()) {
                        boardCards.get(i).setIcon(_cardImgs[c.suit().ordinal()][c.rank().ordinal()]);
                        boardCards.get(i).setVisible(true);
                        i++;
                    }
                    
                    logArea.append("%%% Round [" + r.toString() + "] is starting %%%\n");
                }
            });
        } catch (Exception e) {
            
        }
    }

    @Override
    public void roundEnd(ITable table, ITable.Round r) {
        
    }

    @Override
    public void newHand(final ITable table) {
        _holeCards.clear();
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                potLabel.setText("Pot: 0");
                for (JLabel boardCard : boardCards) {
                    boardCard.setVisible(false);
                    boardCard.setText("");
                }
                for (JLabel plAmt : plAmts) {
                    plAmt.setVisible(false);
                }
                for (JLabel plSt : plSts) {
                    plSt.setText("");
                    plSt.setVisible(true);
                }
                
                plSts.get(getPlayer(table.playerSmallBlind())).setText("SB");
                plSts.get(getPlayer(table.playerBigBlind())).setText("BB");
                if (table.numberPlayers() == 2) {
                    plSts.get(getPlayer(table.playerSmallBlind())).setText("[D] SB");                    
                }
                else {
                    plSts.get(getPlayer(table.playerDealer())).setText("[D]");
                }

                holeC1.setVisible(false);
                holeC2.setVisible(false);
            }

        });
    }

    @Override
    public void newGame(ITable table, final int stackSize, int sBlind, int bBlind, final List<String> players) {
        SwingUtilities.invokeLater(new Runnable() {       
            @Override
            public void run() {
                _players = players;
                IdLabel.setText(_id);
                logArea.setText("");
                for (JLabel plId : plIds) {
                    plId.setVisible(false);
                }

                int i = 0;
                for (String pl : _players) {
                    plIds.get(i).setText(pl + " (" + stackSize + ")");
                    plIds.get(i).setVisible(true);
                    i++;
                }
            }
        });
    }
    
    private int getPlayer(String id) {
        int i = 0;
        for (String pl : _players) {
            if (id.equals(pl)) {
                return i;
            }
            i++;
        }
        return -1;
    }

    @Override
    public void winHand(ITable table, final IPlayerData data, final int amount) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                setPlayerData(data);
                logArea.append("### [" + data.id() + "] wins the hand (" + amount + ")\n");
            }
        });
    }

    @Override
    public void gameAction(final ITable table, final IPlayerData plData, final ITable.ActionType type, final int amount) {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    setPlayerData(plData);
                    if (type == ActionType.FOLD) {
                        int idx = getPlayer(plData.id());
                        JLabel st = plSts.get(idx);
                        st.setText(st.getText() + " [FOLD]");
                    }
                    if (amount == 0) {
                        logArea.append("[" + plData.id() + "] ACTION: " + type.toString() + "\n");                    
                    }
                    else {
                        logArea.append("[" + plData.id() + "] ACTION: " + type.toString() + " (" + amount + ")\n");                                        
                    }
                    potLabel.setText("Pot: " + table.potSize());
                }
            });
        } catch (Exception e) {
            
        }
    }
    
    private void setPlayerData(IPlayerData plData) {
        int idx = getPlayer(plData.id());
        
        plIds.get(idx).setText(plData.id() + " (" + plData.stack() + ")");
        if (plData.moneyInPotForRound() > 0) {
            plAmts.get(idx).setText(Integer.toString(plData.moneyInPotForRound()));
            plAmts.get(idx).setVisible(true);
        }
    }

    @Override
    public void leave(ITable table, final IPlayerData plData) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                int idx = getPlayer(plData.id());
                //plIds.get(idx).setVisible(false);
                plAmts.get(idx).setVisible(false);
                plSts.get(idx).setVisible(false);
                logArea.append("[" + plData.id() + "] left the table\n");
            }
        });
    }
}
import java.util.ArrayList;
import java.util.List;
import shaft.poker.agent.IBettingStrategy;
import shaft.poker.agent.IHandEvaluator;
import shaft.poker.agent.IHandRange;
import shaft.poker.agent.PlayerAgent;
import shaft.poker.agent.bettingstrategy.NeuralNetStrategy;
import shaft.poker.agent.bettingstrategy.SimpleStrategy;
import shaft.poker.agent.handevaluators.CompoundHandEval;
import shaft.poker.agent.handevaluators.PreflopHandGroups;
import shaft.poker.agent.handevaluators.enumeration.HeuristicPPotEval;
import shaft.poker.agent.handevaluators.enumeration.RawStrEval;
import shaft.poker.agent.handevaluators.enumeration.bucket.BucketEnum1CardHandEval;
import shaft.poker.agent.handranges.IPlayerRange;
import shaft.poker.agent.handranges.weightedrange.CompositeFieldRange;
import shaft.poker.agent.handranges.weightedrange.IFrequencyTable;
import shaft.poker.agent.handranges.weightedrange.PlayerWTRange;
import shaft.poker.agent.handranges.weightedrange.frequencytable.GenericFrequencyTable;
import shaft.poker.agent.handranges.weightedrange.frequencytable.PlayerSpecificFrequencyTable;
import shaft.poker.agent.handranges.weightedrange.weighttable.WeightTable;
import shaft.poker.game.*;
import shaft.poker.game.components.*;
import shaft.poker.game.table.PokerTable;
import shaft.poker.game.table.actionbuilder.LimitRules;
import shaft.poker.humanplayer.HumanPlayer;
import shaft.poker.humanplayer.HumanPlayerGUI;
/*
 * The MIT License
 *
 * Copyright 2013 Thomas Schaffer <thomas.schaffer@epitech.eu>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package shaft.poker.factory;


/**
 *
 * @author Thomas Schaffer <thomas.schaffer@epitech.eu>
 */
public class PokerFactory {
    
    private List<IPlayer> _players;
    private PokerTable _table;
    
    private List<CompositeFieldRange> _fieldRanges;
    private List<IPlayerRange> _playerRanges;
    
    private boolean _genericModel;
    
    private static String[] _agentNames = {
        "Boris",
        "Jeff",
        "John",
        "Clark",
        "Marcel",
        "Mary",
        "Anna",
        "Joan",
        "Batman"
    };
    private static int _nameCount = 0;
    
    
    public PokerFactory() {
        _players = new ArrayList<>(10);
        _fieldRanges = new ArrayList<>(10);
        _playerRanges = new ArrayList<>(10);
    }
    
    public void genericModel(boolean generic) {
        _genericModel = generic;
    }
    
    private IFrequencyTable buildFreqTable(String id) {
        if (_genericModel) {
            return new GenericFrequencyTable();
        }
        return new PlayerSpecificFrequencyTable(id, _table);
    }
    
    public ITable newLimitTable() {
        _table = new PokerTable(new LimitRules());
        return _table;
    }
    
    public void addHumanPlayer() {
        CompositeFieldRange compRange = new CompositeFieldRange(_table, new WeightTable(_table));
        addPlayer(new HumanPlayer(_table), compRange);
    }
       
    public IPlayer addGUIHumanPlayer() {
        CompositeFieldRange compRange = new CompositeFieldRange(_table, new WeightTable(_table));
        HumanPlayerGUI gui = new HumanPlayerGUI(_table);
        addPlayer(gui, compRange);
        return gui;
    }
    
    public void addSimpleAgent() {
        String id = _agentNames[_nameCount++];
        addAgent(new HeuristicPPotEval(), new SimpleStrategy(_table), id);
    }
    
    public IPlayer addBlankNeuralNetAgent() {
        String id = _agentNames[_nameCount++];
        return addAgent(new HeuristicPPotEval(), new NeuralNetStrategy(_table), id);
    }
    
    public IPlayer addTrainedNeuralNetAgent(String name) {
        PlayerAgent agent = addAgent(new HeuristicPPotEval(), new NeuralNetStrategy(_table), name);