private int zeroExplore(int startPostion, int depth, int dicrection, int lastFoundPlayer, LongBoard state) {
            if(depth == 0) {
                return lastFoundPlayer;
            }
            long mask = 1l <<(startPostion + dicrection);

            if((state.boards[0] & mask) != 0) {
                if(lastFoundPlayer == 0 || lastFoundPlayer == -1) {
                    return zeroExplore(startPostion + dicrection, depth-1, dicrection, 0, state);
                }
                return -1;
            } else if ((state.boards[1] & mask) != 0) {
                if(lastFoundPlayer == 1 || lastFoundPlayer == -1) {
                    return zeroExplore(startPostion + dicrection, depth-1, dicrection, 1, state);
                }
                return -1;
            } else {
                return -1;
            }
        }
    }

    /**
     * A heuristic that considers moves to win.
     */
    private class MovesToWin implements Heuristic<MovesToWin.MTWData> {

        public MTWData createHeuristic() { return new MTWData(); }
        public MTWData moveHeuristic(MTWData oldData, int column, int player) {
            // First duplicate and update the board
            MTWData newData = new MTWData(oldData);
            newData.move(column, player);

            // Find the new MTWCs for both players and return
            updateMTWCs(newData);
            return newData;
        }

        /**
         * Traces the number of coins and free spaces (tupled) in the given direction.
         */
        private Tuple<Integer, Integer> traceDirection(Set<Tuple<Integer, Integer>> set,
                                                       int board[][], int player,
                                                       int startX, int startY, int dx, int dy) {
            Tuple<Integer, Integer> res = new Tuple<>(0, 0);
            // Return if we are outside the board
            if ((startX >= x || startX < 0) || (startY >= y || startY < 0)) return res;
            int value = board[startX][startY];

            // Add the coordinate
            if (value == player) {
                set.add(new Tuple<>(startX, startY));
                res._1++;
            } else if (value == 0) {
                res._2++;
            }

            // Traverse recursively if the other player is not in the way
            if (value == 0 || value == player) {
                Tuple<Integer, Integer> newRes =
                        traceDirection(set, board, player, startX + dx, startY + dy, dx, dy);
                res._1 += newRes._1;
                res._2 += newRes._2;
            }

            return res;
        }

        private void addMTWCs(MTWData data, int x, int y, int dx, int dy) {
            // Create set of tuples with coordinates - with annoying syntax
            Set<Tuple<Integer, Integer>> set = new HashSet<Tuple<Integer, Integer>>();
            // Trace in the given direction and it's opposite direction
            Tuple<Integer, Integer> first  = traceDirection(set, data.board, data.player,      x,       y,  dx,  dy);
            Tuple<Integer, Integer> second = traceDirection(set, data.board, data.player, x - dx,  y - dx, -dx, -dy);
            // Add to heuristic data if not empty and the WC is large enough to give a win
            if (!set.isEmpty() && first._1 + first._2 + second._1 + second._2 > 3) {
                int mTW = 4 - first._1 + second._1;

                if (data.player == playerID) {
                    data.mTWCFP1.add(set);
                    if (mTW < data.mTW1) data.mTW1 = mTW;
                }
                else {
                    data.mTWCFP2.add(set);
                    if (mTW < data.mTW2) data.mTW2 = mTW;
                }

            }
        }

        /**
         * Removes MTWCs from the opponent that are 'destroyed' by the latest move.
         */
        private void removeBrokenMTWCs(MTWData data) {
            Set<Set<Tuple<Integer, Integer>>> set;
            if (data.player == playerID) {
                set = data.mTWCFP2;
            } else {
                set = data.mTWCFP1;
            }
            Iterator<Set<Tuple<Integer, Integer>>> combinations = set.iterator();
            while (combinations.hasNext()) {
                Set<Tuple<Integer, Integer>> combination = combinations.next();
                for (Tuple<Integer, Integer> coordinate : combination) {
                    // Remove the combination if it contains the current coordinate
                    // (safely by using the iterator remove)
                    if (coordinate._1.equals(data.column) && coordinate._2.equals(data.row)) {
                        combinations.remove();
                        break;
                    }
                }
            }
        }

        private void updateMTWCs(MTWData data) {
            // Delete old MTWCs and reset the MTW for the current player
            if (data.player == playerID) {
                data.mTWCFP1 = new HashSet<>();
                data.mTW1    = 4;
            } else {
                data.mTWCFP2 = new HashSet<>();
                data.mTW2    = 4;
            }


            // Remove old MTWCs
            removeBrokenMTWCs(data);

            // Trace horizontal
            addMTWCs(data, data.column, data.row, 1, 0);

            // Trace vertical
            addMTWCs(data, data.column, data.row, 0, 1);

            // Trace from left to right
            addMTWCs(data, data.column, data.row, -1, 1);

            // Trace from right to left
            addMTWCs(data, data.column, data.row, -1, -1);
        }

        public Tuple<Float, MTWData> h(LongBoard board, MTWData data) {
            // Calculate and return heuristic value (between -1 and 1)
            float h = (data.mTW1 - data.mTW2 * 1.25f) / 5;
            return new Tuple<>(h, data);
        }

        /**
         * Data for the MTW heuristic containing the board (with the moves) and a list of the possible wins.
         */
        class MTWData implements HeuristicData {
            // The board for the current state
            int board[][] = new int[x][y];
            // Moves to win combinations for player 1 (mTWCFP1)
            Set<Set<Tuple<Integer, Integer>>> mTWCFP1;
            // Moves to win combinations for player 2 (mTWCFP2)
            Set<Set<Tuple<Integer, Integer>>> mTWCFP2;

            int column, row, player, mTW1, mTW2;

            /**
             * Creates a MTWData board with no initial coins set.
             */
            MTWData() {
                mTWCFP1 = new HashSet<>(); mTWCFP2 = new HashSet<>();
                mTW1 = 4; mTW2 = 4;
            }

            /**
             * Constructs a copy of a MTWData object.
             */
            MTWData(MTWData old) {
                for (int i = 0; i < x; i++) {
                    System.arraycopy(old.board[i], 0, board[i], 0, y);
                }
                this.mTWCFP1 = new HashSet<>(old.mTWCFP1);
                this.mTWCFP2 = new HashSet<>(old.mTWCFP2);
                this.mTW1 = old.mTW1;
                this.mTW2 = old.mTW2;
            }

            /**
             * Adds a coin to the current board in the given column.
             */
            public void move(int column, int player) {
                this.column = column;
                this.player = player;
                for (row = 0; row < y; row++) {
                    if (board[column][row] == 0) {
                        board[column][row] = player;
                        break;
                    }
                }
            }
        }
    }

    /**
     * A board that is based on a representation of a single long per player.
     */
    public class LongBoard {
        // -- The following comments are made for Sigurt, who cannot see the errors in his ways -- //
        // -- Jens is ukraine                                                                   -- //
        long boards[]; // Zero-indexed boards for player one (0) and player two (1).
        byte height[]; // The largest index of the columns where a coin has been inserted.
        int player = -1; // Set player to -1 to avoid the hasWon method to check for the wrong player
                         // (because player is incremented whenever a move has been made)
        int HEIGHT,  // The height of the board
            WIDTH,   // The width of the board
            H1,      // The height of the board PLUS 1 (hence the 1)
            H2,      // The height of the board PLUS 2 (hence the 2)
            SIZE,    // The size of the board (height * width)
            SIZE1,   // The size of the board PLUS 1 (height * width) + 1 (hence the 1)
            COLUMN;  // All bytes filled in one single column, found by shifting 1 H1 bytes to the left (to the left).

        long ALL,    // All bytes in the board ignited.
             BOTTOM, // A long with all the bytes in the bottom row ignited.
             TOP;    // A long with all the bytes in the top row ignited.

        public LongBoard(int width, int height) {
            if (((width * height) + width) > 64) {
                boardBroken = true;
            }
            init(width, height);
        }

        public LongBoard(LongBoard old) {
            init(old.WIDTH, old.HEIGHT);
            System.arraycopy(old.boards, 0, this.boards, 0, 2);
            System.arraycopy(old.height, 0, this.height, 0, WIDTH);
            player = old.player;
        }

        private void init(int width, int height) {
            assert (height * width < 63);
            HEIGHT = height;
            WIDTH = width;
            boards = new long[2];
            H1 = HEIGHT + 1;
            H2 = HEIGHT + 2;
            SIZE = HEIGHT * WIDTH;
            SIZE1 = H1 * WIDTH;
            COLUMN = (1 << H1)- 1;
            ALL = (1L << SIZE1) - 1L;
            BOTTOM = ALL / COLUMN;
            TOP = BOTTOM << HEIGHT;

            this.height = new byte[width];
            for (int i = 0; i < WIDTH; i++) {
                this.height[i] = (byte) (H1 * i);
            }
        }

        boolean hasWon() {
            long board = boards[player & 1]; // Awesome page at https://codebrew.io/
            long diagonalLeft  = board & (board >> HEIGHT);
            long horizontal    = board & (board >> H1);
            long diagonalRight = board & (board >> H2);
            long vertical      = board & (board >> 1);
            return ((diagonalLeft & (diagonalLeft >> 2 * HEIGHT)) |
                    (horizontal & (horizontal >> 2* H1)) |
                    (diagonalRight & (diagonalRight >> 2 * H2)) |
                    (vertical & (vertical >> 2))) != 0;
        }

        boolean isPlayable(int column) {
            return ((boards[player & 1] | 1L << height[column]) & TOP) == 0;
        }

        void move(int column) {
            player++;
            boards[player & 1] ^= 1L << height[column]++;
        }

        private int getBit(long l, int n){
            return (int)((l >> n) & 1L);
        }

        @Override
        public String toString() {
            StringBuffer sBuff = new StringBuffer();
            for (int i=0; i < SIZE1; i++){
                if ((i+1)%(H1) == 0) continue;
                int firstBoard = getBit(boards[0], i);
                int secondBoard = getBit(boards[1], i);
                String slotState ="";
                if (secondBoard == 1) {
                    slotState = "o";
                } else if (firstBoard == 1) {
                    slotState = "x";
                } else {
                    slotState = "b";
                }

                sBuff.append(slotState);
                if (i < SIZE1 -2){
                   sBuff.append(",");
                }
            }
            return sBuff.toString();
        }

    }

    //initialize knowledge base from file
    private void initKnowledge(){
        knowledgeBase = new HashMap<>();
        try {
            BufferedReader br = new BufferedReader(new FileReader("connect-4.data"));
            String line;
            while ((line = br.readLine()) != null){
                int commaIdx = line.lastIndexOf(",");
                System.out.println(line.substring(0, commaIdx));
                knowledgeBase.put(line.substring(0, commaIdx), Float.parseFloat(line.substring(commaIdx + 1)));
            }
            br.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Why you no have connect-4.data in folder?");
        } catch (IOException e) {
            throw new RuntimeException("Some IO went wrong me thinks");
        }