private Tuple<Float, HeuristicData> max(LongBoard state, HeuristicData data, float alpha,
                                            float beta, int action, int depth) {
        Winner win = gameFinished(state);
        statesChecked++;
        Tuple<Float, HeuristicData> y = new Tuple<>((float) Integer.MIN_VALUE, null);

        if(win != Winner.NOT_FINISHED) {
            float value = utility(win, depth);
            cache.put(state.toString(), value);
            return new Tuple<>(value, null);
        }

        if (cache.get(state.toString()) != null) {
            cacheHits++;
			return new Tuple<>(cache.get(state.toString()),null);
		}
        if (depth == 0) {
            hasReachedMaxDepth = true;
            HeuristicData newData = H.moveHeuristic(data, action, opponentID);
            Tuple<Float, HeuristicData> value = h(state, newData);
            cache.put(state.toString(), value._1);
            return value;
        }

        for (int newaction : generateActions(state)) {
            // Stop if time's up
            if (isTimeUp()) break;
            HeuristicData newData = H.moveHeuristic(data, newaction, opponentID);
            Tuple<Float, HeuristicData> min = min(result(state, newaction), newData, alpha, beta,newaction, depth - 1);
            if (min._1 > y._1) {
                y = min;
            }
            // tests for possible beta cut
            if (y._1 >= beta) {
                cutoffs++;
                cache.put(state.toString(), y._1);
                return y;
            }

            alpha = Math.max(alpha, y._1);

        }
        cache.put(state.toString(), y._1);
        return y;
    }