#include<bits/stdc++.h>
#include<stdio.h>
#include<map>
#include <ext/pb_ds/assoc_container.hpp> // Common file 
#include <ext/pb_ds/tree_policy.hpp> 
using namespace __gnu_pbds;
using namespace std;
typedef long long int ll;
typedef long double ld;
typedef unsigned long long int lu;
typedef vector< int > vi;
typedef vector < ll > vl;
typedef set <ll> sl;
typedef vector< vi > vvi;
typedef vector< vl > vvl;
typedef pair< int,int > ii;
typedef pair< ll,ll > il;
#define sz(a) ll((a).size())
#define test(t) ll t;cin>>t;while(t--)
#define pb push_back
#define all(c) (c).begin(),(c).end()
#define tr(c,i) for(__typeof((c).begin()) i = (c).begin(); i != (c).end(); i++)
#define present(c,x) ((c).find(x) != (c).end())
#define cpresent(c,x) (find(all(c),x) != (c).end())
#define fastio ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define Vi vector
//#define pr pair
#define mpr make_pair
#define maxheap priority_queue<ll>
#define minheap priority_queue<ll,vector<ll>,greater<ll>>
#define fi first
#define se second
#define isnum(a) (a>=48 && a<=57)
#define isS(a) (a>='a' && a<='z')
#define isU(a) (a>='A' && a<='Z')
#define toS(a) (isU(a)?a+32:a)
#define toU(a) (isS(a)?a-32:a)
#define toC(a) a&15
 
//set <ll> st[100005];
ll d[100005];
 
vector<char> prime (100000+1, true);
void sieve(ll n)
{   
    //O(nloglogn) time
    prime[0] = prime[1] = false;
    for (ll i=2; i<=n; ++i)
    {
        if (prime[i])
        {
           // st[i].insert(i);            
            for (ll j=2*i; j<=n; j+=i)
            {
                prime[j] = false;
                //st[j].insert(i);
            }
        }
    }
}
#define forn(i,e) for(int i = 0; i < e; i++)
#define rep(i,a,b) for(i=a;i<b;i++)
#define REP(i,a,b) for(i=a;i<=b;i++)
#define reprev(i,a,b) for(i=a;i>=b;i--)
 
// Convex Hull Trick
typedef ll LT;
typedef pair<LT,LT> LP;
struct CHT {
  deque<LP> d;
  LT get(LT x) {
    while (sz(d) >= 2) {
      LT a = d[0].fi*x + d[0].se;
      LT b = d[1].fi*x + d[1].se;
      if (a <= b) break; // get min
      d.pop_front();
    }
    return d[0].fi*x + d[0].se;
  }
  void add(LP x) { // decreasing order of x.fi
    while (sz(d) >= 2) {
      LP y = d[sz(d) - 1];
      LP z = d[sz(d) - 2];
      if ((x.se-y.se)*(z.fi-y.fi) >= (y.fi-x.fi)*(y.se-z.se)) break; // both
      d.pop_back();
    }
    d.pb(x);
  }
};
//

typedef tree<ll, null_type, less<ll>, rb_tree_tag, 
             tree_order_statistics_node_update> 
    indexed_set;
 
#define leadzero(a) __builtin_clz(a) //count leading zeros
#define trailzero(a) __builtin_ctz(a) //count trailing zeros
#define bitcount(a) __builtin_popcount(a) // count set bits (add ll)
#define ln cout<<"\n"
//#define sp cout<<" "
//#define mp make_pair
#define maxe(a) *max_element(a.begin(),a.end())
#define maxi(a) max_element(a.begin(),a.end())-a.begin()
#define mine(a) *min_element(a.begin(),a.end())
#define mini(a) min_element(a.begin(),a.end())-a.begin()
//ll gcd(ll a, ll b) { if(b == 0) return a; return gcd(b, a % b); }
ll fastpowMOD(ll a, ll p,ll MOD){ if(p==0) return 1; ll z = fastpowMOD(a,p/2,MOD); z = (z*z)%MOD; if(p%2) z = (z*a)%MOD; return z; }
ll fastpow(ll a, ll p){ if(p==0) return 1; ll z = fastpow(a,p/2); z = (z*z); if(p%2) z = (z*a); return z; }
//CHT stands for convex hull trick
ll search1(ll a[], ll f,ll n)
{
    ll left=1;
    ll right=n+1;
    while(right-left>1)
    {
        ll mid = left + (right-left)/2;
        if(a[mid]<f)
        {
            left=mid;
        }
        else right=mid;
    }
    return a[left];
}

void convert_reduce(ll arr[], ll n) 
{ 
    vector <pair<ll, ll> > v; 
    for (ll i = 0; i < n; i++)v.push_back(make_pair(arr[i], i)); 
    sort(v.begin(), v.end());
    ll cnt = 0; 
    for (ll i=0; i<n; i++)
    {
        arr[v[i].second] = cnt;
        ll j = i;
        while(j<n && v[j].fi==v[i].fi)arr[v[j++].second] = cnt;
        i=j-1;
        cnt++;
    } 
}
 
const ll INF = 1e18;
vector<ll> lazy;
vector<ll> TREE;
const ll MOD = (ll)1e12+7; 
void updateRange(ll node, ll start, ll end, ll l, ll r, ll val)
{
    if(lazy[node] != 0)
    { 
        // This node needs to be updated
        TREE[node] += lazy[node];    // Update it
        if(start != end)
        {
            lazy[node*2] += lazy[node];                  // Mark child as lazy
            lazy[node*2+1] += lazy[node];                // Mark child as lazy
        }
        lazy[node] = 0;                                  // Reset it
    }
    if(start > end or start > r or end < l)              // Current segment is not within range [l, r]
        return;
    if(start >= l and end <= r)
    {
        // Segment is fully within range
        lazy[node] +=  val;
        if(start != end)
        {
            // Not leaf node
            lazy[node*2] += lazy[node];
            lazy[node*2+1] += lazy[node];
        }
        if(TREE[node]==MOD)TREE[node]=0;
        TREE[node]+=lazy[node];
        lazy[node]=0;
        return;
    }
    ll mid = (start + end) / 2;
    updateRange(node*2, start, mid, l, r, val);        // Updating left child
    updateRange(node*2 + 1, mid + 1, end, l, r, val);   // Updating right child
    TREE[node] = min(TREE[node*2],TREE[node*2+1]);        // Updating root with max value 
}
 
ll queryRange(ll node, ll start, ll end)
{
    if(lazy[node] != 0)
    {
        // This node needs to be updated
        TREE[node] += lazy[node];            // Update it
        if(start != end)
        {
            lazy[node*2] += lazy[node];         // Mark child as lazy
            lazy[node*2+1] += lazy[node];    // Mark child as lazy
        }
        lazy[node] = 0;                 // Reset it
    }
 
    if(start==end)return start;
 
    ll mid = (start + end) / 2;
    if(lazy[2*node+1] != 0)
    {
        TREE[2*node+1]+=lazy[2*node+1];
        ll nd = 2*node+1;
        if(mid+1!=end)
        {
            lazy[nd*2] += lazy[nd];         // Mark child as lazy
            lazy[nd*2+1] += lazy[nd];    // Mark child as lazy
        }
        lazy[2*node+1]=0;
    }
    if(lazy[2*node] != 0)
    {
        TREE[2*node]+=lazy[2*node];
        ll nd = 2*node;
        if(start!=mid)
        {
            lazy[nd*2] += lazy[nd];         // Mark child as lazy
            lazy[nd*2+1] += lazy[nd];    // Mark child as lazy
        }
        lazy[2*node]=0;
    }
    if(TREE[2*node+1]==0)
    {
        ll a = queryRange(2*node+1,mid+1,end);
        TREE[node]=min(TREE[2*node],TREE[2*node+1]);
        return a;
    }
    else
    {
        ll a = queryRange(2*node,start,mid);
        TREE[node]=min(TREE[2*node],TREE[2*node+1]);
        return a;
    }   
}
ll dfs(ll bit, vl& a)
{
    if(bit==-1)return 0;
    vl s0,s1;
    s0.clear();
    s1.clear();
    ll i,j;
    rep(i,0,sz(a))
    {
        if((fastpow(2,bit)&a[i])==0)s0.pb(a[i]);
        else s1.pb(a[i]);
    }
    if(s1.empty())
    {
        return dfs(bit-1,s0);
    }
    if(s0.empty())return dfs(bit-1,s1);
    return fastpow(2,bit)+min(dfs(bit-1,s0), dfs(bit-1,s1));
}


int main(){
    fastio;
    ll h,w;
    cin>>h>>w;
    ll i,j;
    string s[h];
    rep(i,0,h)cin>>s[i];
    vector<pair<ll,ll> > v;
    rep(i,0,h)
    {
        rep(j,0,w)
        {
            if(s[i][j]=='.')v.pb({i,j});
        }
    }
    ll ans = 0;
    rep(i,0,sz(v)-1)
    {
        rep(j,i+1,sz(v))
        {
            ll br[h][w];
            memset(br,0,sizeof(br));
            pair<ll,ll> src = v[i];
            pair<ll,ll> dest = v[j];
            queue<pair<pair<ll,ll>, ll> > q;
            q.push({src, 0});
            br[src.fi][src.se]=1;
            while(!q.empty())
            {
                pair<ll,ll> tmp = q.front().fi;
                ll dist = q.front().se;
                q.pop();
                if(tmp==dest)
                {
                    ans=max(ans, dist);
                    break;
                }
                if(tmp.fi+1<h && br[tmp.fi+1][tmp.se]==0 && s[tmp.fi+1][tmp.se]=='.')
                {
                    br[tmp.fi+1][tmp.se]=1;
                    q.push({{tmp.fi+1, tmp.se}, dist+1});
                }
                if(tmp.se+1<w && br[tmp.fi][tmp.se+1]==0 && s[tmp.fi][tmp.se+1]=='.')
                {
                    br[tmp.fi][tmp.se+1]=1;
                    q.push({{tmp.fi, tmp.se+1}, dist+1});
                }
                if(tmp.fi-1>=0 && br[tmp.fi-1][tmp.se]==0 && s[tmp.fi-1][tmp.se]=='.')
                {
                    br[tmp.fi-1][tmp.se]=1;
                    q.push({{tmp.fi-1, tmp.se}, dist+1});
                }
                if(tmp.se-1>=0 && br[tmp.fi][tmp.se-1]==0 && s[tmp.fi][tmp.se-1]=='.')
                {
                    br[tmp.fi][tmp.se-1]=1;
                    q.push({{tmp.fi, tmp.se-1}, dist+1});
                }
            }
            //q.clear();
        }
    }
    cout<<ans<<endl;
    return 0;
}

Measured memory_bytes: 384