import java.util.concurrent.LinkedBlockingDeque

fun main(args: Array<String>) {
    data class Route(val x: Int, val y: Int, val length: Int)

    fun genNext(x: Int, y: Int, length: Int, maze: List<MutableList<Char>>, queue: LinkedBlockingDeque<Route>) {
        maze[y][x] = '#'
        queue.offer(Route(x, y, length + 1))
    }

    val (h, w) = readLine()?.split(" ")?.map(String::toInt) ?: return
    val maze: List<MutableList<Char>> = 0.until(h).map {
        readLine()?.toMutableList() ?: return
    }
    val queue = LinkedBlockingDeque<Route>()
    var max = 0
    0.until(h).forEach { y ->
        0.until(w).forEach loop@{ x ->
            if (maze[y][x] == '#') {
                return@loop
            }
            val mazeCopy = maze.map { it.toMutableList() }
            genNext(x, y, 0, mazeCopy, queue)
            while (queue.isNotEmpty()) {
                val route = queue.poll()
                val length = route.length

                max = Math.max(max, length)

                val xx = route.x
                val yy = route.y
                // 上
                val up = yy - 1
                if (up >= 0 && mazeCopy[up][xx] != '#') {
                    genNext(xx, up, length, mazeCopy, queue)
                }
                // 左
                val left = xx - 1
                if (left >= 0 && mazeCopy[yy][left] != '#') {
                    genNext(left, yy, length, mazeCopy, queue)
                }
                // 右
                val right = xx + 1
                if (right < w && mazeCopy[yy][right] != '#') {
                    genNext(right, yy, length, mazeCopy, queue)
                }
                // 下
                val down = yy + 1
                if (down < h && mazeCopy[down][xx] != '#') {
                    genNext(xx, down, length, mazeCopy, queue)
                }
            }
        }
    }
    println(max - 1)
}
