#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <stdlib.h>
#include <string.h>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <stdio.h>
#include <tuple>
#include <bitset>
#include <cfloat>
#include <fstream>
#include <limits.h>
#include <list>
#include <math.h>
#include <random>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <numeric>
#include <utility>
#include <sstream>

template<class T> inline bool chmin(T& a, T b) {
	if (a > b) {
		a = b;
		return true;
	}
	return false;
}
template<class T> inline bool chmax(T& a, T b) {
	if (a < b) {
		a = b;
		return true;
	}
	return false;
}

using namespace std;
#define int long long
#define ll long long
#define rep(i, n) for (ll i = 0; i < (n); i++)
#define FOR(i,a,b) for(ll i=(a);i<(b);i++)
#define FORR(i,a,b)for(ll i=(a);i<=(b);i++)
#define repr(i,n) for(ll i=n;i>=0;i--)
#define P pair<ll, ll>
#define sz(x) (ll)x.size()
#define ALL(x) (x).begin(),(x).end()
#define ALLR(x) (x).rbegin(),(x).rend()
#define VE vector<ll>
#define COUT(x) cout<<(x)<<endl
#define MA map<ll,ll>
#define SE set<ll>
#define PQ priority_queue<ll>
#define PQR priority_queue<ll,VE,greater<ll>>
#define COUT(x) cout<<(x)<<endl
#define YES(n) cout << ((n) ? "YES" : "NO"  ) << endl
#define Yes(n) cout << ((n) ? "Yes" : "No"  ) << endl
#define EPS (1e-10)
ll gcd(ll a, ll b) {
	if (a < b)swap(a, b);
	if (b == 0) return a;
	unsigned r;
	while ((r = a % b)) {
		a = b;
		b = r;
	}
	return b;
}
ll lcm(ll a, ll b) {
	ll g = gcd(a, b);
	return a / g * b;
}
bool prime(ll n) {
	for (ll i = 2; i <= sqrt(n); i++) {
		if (n%i == 0)return false;
	}
	return n != 1;
}
map<ll, ll> prime_factor(ll n) {
	map<ll, ll> ret;
	for (ll i = 2; i * i <= n; i++) {
		while (n % i == 0) {
			ret[i]++;
			n /= i;
		}
	}
	if (n != 1) ret[n] = 1;
	return ret;
}

const long long MOD = 1000000007;
//const long long MOD = 998244353;
const long long INF = 1LL << 60;



const long double PI = 3.14159265359;
const int MAX_N = 110;
const int MAX_M = 110;
// 移動4方向のベクトル
const int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1 };
// 入力
char maze[MAX_N][MAX_M + 1];	// 迷路を表す文字列の配列
int n, m;
int sx=0, sy=0;						// スタートの座標
int gx=0, gy=0;						// ゴールの座標
int dist[MAX_N][MAX_M];			// 各点までの最短距離の配列

// (sx,sy)から(gx,gy)への最短距離を求める
// 辿り着けないとINF
int bfs() {
	queue<P>que;
	// すべての点をINFで初期化
	rep(i, n)rep(j, m)dist[i][j] = INF;
	// スタート地点をキューに入れ、その点の距離を0にする
	que.push(P(sx, sy));
	dist[sx][sy] = 0;

	// キューが空になるまでループ
	while (!que.empty()) {
		// キューの先頭を取り出す
		P p = que.front(); que.pop();
		// 取り出してきた状態がゴールなら探索をやめる
		if (p.first == gx && p.second == gy)break;
		// 移動4方向をループ
		rep(i, 4) {
			// 移動した後の点を(nx,ny)とする
			int nx = p.first + dx[i], ny = p.second + dy[i];
			// 移動が可能かの判定とすでに訪れたことがあるかの判定(dist[nx][ny]!=INFなら訪れたことがある)
			if (0 <= nx && nx < n && 0 <= ny && ny < m&&maze[nx][ny] != '#'&&dist[nx][ny] == INF) {
				// 移動できるならキューに入れ、その点の距離をpからの距離+1で確定する
				que.push(P(nx, ny));
				dist[nx][ny] = dist[p.first][p.second] + 1;
			}
		}
	}
	return dist[gx][gy];
}
signed main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	rep(i, n)rep(j, m)cin >> maze[i][j];
	int ans = 0;
	rep(i, n) {
		rep(j, m) {
			rep(k, n) {
				rep(l, m) {
					if(maze[i][j]=='.')sx = i,sy = j;
					else continue;
					if (maze[k][l] == '.') {
						gx = k; gy = l;
					}
					else continue;
					chmax(ans,bfs());
				}
			}
		}
	}
	cout << ans << endl;
	return 0;
}

Measured memory_bytes: 256