
fun main(args:Array<String>):Unit {
    repeat(readLine()!!.toInt()){
        println(countRensa(Array(12){readLine()!!.trim()}))
    }
}
class UnionFindTree{
    private var mRank:Int = 1
    private var mParent:UnionFindTree? = null
    private fun parent():UnionFindTree {
        return when(mParent){
            null -> this
            else -> {
                mParent = mParent?.parent()
                mParent!!
            }
        }
    }
    fun rank():Int {
        return when(mParent){
            null -> mRank
            else -> parent().rank()
        }
    }
    fun isSameUnion(other:UnionFindTree):Boolean {
        return parent() == other.parent()
    }
    fun merge(other:UnionFindTree):Unit {
        if (!isSameUnion(other)){
            val newRank = rank() + other.rank()
            parent().mParent = other.parent()
            parent().mRank = newRank
        }
    }
}
data class Point(val x:Int, val y:Int) {
    fun neighbors():Iterable<Point> = listOf(Point(x - 1, y), Point(x + 1, y), Point(x, y - 1), Point(x, y + 1))
}
data class Puyo(val point:Point, val color:Char){
    fun down():Puyo = Puyo(Point(point.x, point.y + 1), color)
}
fun countRensa(state:Array<String>):Int {
    //println(state.joinToString("\n", postfix = "\n"))
    val puyo = state.map{it.map{UnionFindTree()}}
    val moved = state.map(String::toCharArray).toTypedArray()
    for (i in 1 until state.size) for (j in 0 until state[0].length) {
        if (state[i][j] !in listOf('O', '.') && state[i - 1][j] == state[i][j]){
            puyo[i][j].merge(puyo[i - 1][j])
        }
    }
    for (i in 0 until state.size) for (j in 1 until state[0].length){
        if (state[i][j] !in listOf('O', '.') && state[i][j - 1] == state[i][j]){
            puyo[i][j].merge(puyo[i][j - 1])
        }
    }
    for (i in 0 until state.size) for (j in 0 until state[0].length){
        if (puyo[i][j].rank() >= 4){
            moved[i][j] = '.'
            Point(j, i).neighbors().filter{it.y in 0 until state.size && it.x in 0 until state[0].length}.filter { state[it.y][it.x] == 'O' }.forEach {
                moved[it.y][it.x] = '.'
            }
        }
    }
    var movable = (0 until state.size).flatMap{y -> (0 until state[0].length).filter{x -> moved[y][x] != '.'}.map{x -> Puyo(Point(x, y), moved[y][x])}}
    while(movable.count{
                it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.'
            } != 0)  {
        val m = movable.filter{it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.'}
        movable = movable.map{
            if (it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.') {
                Puyo(it.point.copy(y = it.point.y + 1), it.color)
            }else{
                it
            }
        }
        m.forEach {
            moved[it.point.y][it.point.x] = '.'
            moved[it.point.y + 1][it.point.x] = it.color
        }
    }
    return if (puyo.any{it.any{it.rank() >= 4}}) 1 + countRensa(moved.map{it.joinToString("")}.toTypedArray())
        else 0
}
