private MoveAndCost findBestMoveInternal(int[][] board, int allowedCallCnt) {
        allowedCallCnt--; //this call
        int n = board.length;
        int m = board[0].length;
        double minCost = Double.POSITIVE_INFINITY;
        int[][] bestBoard = null;
        Move bestMove = null;

        int[][][] newBoards = getNewBoards(board);
        int needCalls = getNeedCallsForNextDepth(newBoards);
        if(needCalls == 0) {
            return new MoveAndCost(null, evaluator.getFailCost());
        }
        int nextLevelCalls = allowedCallCnt / needCalls; //todo proper rounding?
        for (int dir = 0; dir < 4; dir++) {
            int[][] newBoard = newBoards[dir];
            if(newBoard == null) {
                continue;
            }
            double cost = 0;
            if (nextLevelCalls == 0) {
                cost = evaluator.evaluate(newBoard);
            } else {
                cost = getCost(n, m, nextLevelCalls, newBoard, cost);
            }
            if (cost < minCost) {
                minCost = cost;
                bestMove = Move.ALL[dir];
                bestBoard = newBoard;
            }
        }
        if (allowedCallCnt+1 == maxCallCnt) {
            copyBoard(board, bestBoard);
        }
        return new MoveAndCost(bestMove, minCost);
    }