fun main(args : Array<String>) {
    val (h, w) = readLine()!!.split(" ").map { it.toInt() }
    val maze = Array(h){ readLine()!! }

    var maxDistance = 0
    maze.forEachIndexed { y, row ->
        for (x in row.indices) {
            if (row[x] == '.') {
                // start from here
                val max = bfs(maze, x, y, h, w)
                if (max > maxDistance) maxDistance = max
            }
        }
    }

    println(maxDistance)
}

fun bfs(maze: Array<String>, x: Int, y: Int, h: Int, w: Int): Int {
    val done = Array(h){ Array(w){false} }
    val queue = mutableListOf<Pos>()

    queue.add(Pos(x, y, 0))
    done[y][x] = true

    var maxCost = 0

    while(queue.size > 0) {
        val pos = queue.removeAt(0)
        val newCost = pos.cost + 1

        // left
        if (pos.x > 0 && maze[pos.y][pos.x-1] == '.' && !done[pos.y][pos.x-1]) {
            queue.add(Pos(pos.x-1, pos.y, newCost))
            done[pos.y][pos.x-1] = true
            maxCost = Math.max(maxCost, newCost)
        }
        // right
        if (pos.x < w-1 && maze[pos.y][pos.x+1] == '.' && !done[pos.y][pos.x+1]) {
            queue.add(Pos(pos.x+1, pos.y, newCost))
            done[pos.y][pos.x+1] = true
            maxCost = Math.max(maxCost, newCost)
        }
        // top
        if (pos.y > 0 && maze[pos.y-1][pos.x] == '.' && !done[pos.y-1][pos.x]) {
            queue.add(Pos(pos.x, pos.y-1, newCost))
            done[pos.y-1][pos.x] = true
            maxCost = Math.max(maxCost, newCost)
        }
        // bottom
        if (pos.y < h-1 && maze[pos.y+1][pos.x] == '.' && !done[pos.y+1][pos.x]) {
            queue.add(Pos(pos.x, pos.y+1, newCost))
            done[pos.y+1][pos.x] = true
            maxCost = Math.max(maxCost, newCost)
        }
    }

    return maxCost
}

data class Pos(var x: Int, var y: Int, var cost: Int)