package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"strconv"
)

const (
	initialBufSize = 10000
	maxBufSize     = 1000000
)

var (
	sc *bufio.Scanner = func() *bufio.Scanner {
		sc := bufio.NewScanner(os.Stdin)
		buf := make([]byte, initialBufSize)
		sc.Buffer(buf, maxBufSize)
		sc.Split(bufio.ScanWords)
		return sc
	}()
	mod int = 1e9 + 7
)

func getMaxIndex(S [][]string, startX, startY int) (int, int, int) {
	H := len(S)
	W := len(S[0])

	// costMapの初期化
	costMap := make([][]int, H)
	for i := 0; i < H; i++ {
		costMap[i] = make([]int, W)

		for j := 0; j < W; j++ {
			costMap[i][j] = -1
		}
	}

	costMap[startY][startX] = 0

	// costMapの更新
	max := 0
	maxX := 0
	maxY := 0

	for ; ; {
		changeCnt := 0

		for i := 0; i < H; i++ {
			for j := 0; j < W; j++ {
				if S[i][j] == "#" {
					continue
				}

				if costMap[i][j] >= 0 {
					continue
				}

				var pool []int
				if i > 0 {
					top := costMap[i-1][j]
					if top >= 0 {
						pool = append(pool, top)
					}
				}
				if i < H-1 {
					bottom := costMap[i+1][j]
					if bottom >= 0 {
						pool = append(pool, bottom)
					}
				}
				if j > 0 {
					left := costMap[i][j-1]
					if left >= 0 {
						pool = append(pool, left)
					}
				}
				if j < W-1 {
					right := costMap[i][j+1]
					if right >= 0 {
						pool = append(pool, right)
					}
				}

				if len(pool) == 0 {
					continue
				}

				cost := min(pool...) + 1
				costMap[i][j] = cost
				changeCnt++

				if cost > max {
					max = cost
					maxX = j
					maxY = i
				}
			}
		}

		if changeCnt == 0 {
			break
		}
	}

	//for i := 0; i < H; i++ {
	//	fmt.Println(costMap[i])
	//}

	return maxX, maxY, max
}

func main() {
	H := getInt()
	W := getInt()

	S := make([][]string, H)
	roadCnt := 0
	for i := 0; i < H; i++ {
		S[i] = make([]string, W)

		input := getString()

		for j, c := range input {
			if c == '.' {
				roadCnt++
				S[i][j] = string(c)
			} else {
				S[i][j] = string(c)
			}
		}
	}

	if roadCnt == 2 {
		fmt.Println(1)
		return
	}

	// costMapの初期位置設定
	startX := 0
	startY := 0
	for i := 0; i < H; i++ {
		breakFlag := false
		for j := 0; j < W; j++ {
			if S[i][j] == "." {
				startX = j
				startY = i
				breakFlag = true
				break
			}
		}

		if breakFlag {
			break
		}
	}

	beforeX := startX
	beforeY := startY
	cost := 0
	for ; ; {
		maxX, maxY, max := getMaxIndex(S, beforeX, beforeY)
		//fmt.Println(maxX, maxY, max)

		if cost == max {
			break
		} else {
			beforeX = maxX
			beforeY = maxY
			cost = max
		}
	}

	fmt.Println(cost)
}

func getInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func getString() string {
	sc.Scan()
	return sc.Text()
}

func abs(a int) int {
	return int(math.Abs(float64(a)))
}

func pow(p, q int) int {
	return int(math.Pow(float64(p), float64(q)))
}

func min(nums ...int) int {
	if len(nums) == 0 {
		panic("funciton min() requires at least one argument.")
	}
	res := nums[0]
	for i := 0; i < len(nums); i++ {
		res = int(math.Min(float64(res), float64(nums[i])))
	}
	return res
}

func max(nums ...int) int {
	if len(nums) == 0 {
		panic("funciton max() requires at least one argument.")
	}
	res := nums[0]
	for i := 0; i < len(nums); i++ {
		res = int(math.Max(float64(res), float64(nums[i])))
	}
	return res
}

func strSearch(a []string, b string) bool {
	for i := 0; i < len(a); i++ {
		if a[i] == b {
			return true
		}
	}
	return false
}

func printIntArray(array []int) {
	strArray := fmt.Sprint(array)
	fmt.Println(strArray[1 : len(strArray) - 1])
}

func calcMod(x int) int {
	return x % mod
}
