You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin实现扫雷探索功能时出现StackOverflowError的问题排查

扫雷递归探索栈溢出问题排查

我用Kotlin重写扫雷游戏,实现了无地雷单元格的递归探索逻辑:用户点击无雷单元格时,先检查周围是否有地雷,无雷则递归探索周边单元格,自动展开所有无雷区域。但在9x9全空棋盘上运行时出现StackOverflowError,递归陷入无限循环,求排查逻辑错误。

问题代码

val minesAround = checkIfMinesAround(column, row)
if (!minesAround) {
    explore(column, row)
}

private fun explore(column: Int, row: Int) {
    displayedMinefield[row][column] = Symbol.FCell.symbol
    for (rowNumber in determineRange(row, minefield.lastIndex)) {
        for (columnNumber in determineRange(column, minefield[rowNumber].lastIndex)) {
            if (displayedMinefield[rowNumber][columnNumber] == Symbol.FCell.symbol) continue

            displayedMinefield[rowNumber][columnNumber] = minefield[rowNumber][columnNumber]
            if (!checkIfMinesAround(rowNumber, columnNumber)) {
                explore(rowNumber, columnNumber)
            }
        }
    }
}

private fun checkIfMinesAround(row: Int, column: Int): Boolean {
    var minesAround = false
    for (rowNumber in determineRange(row, minefield.lastIndex)) {
        for (columnNumber in determineRange(column, minefield[rowNumber].lastIndex)) {
            if (minefield[rowNumber][columnNumber] == Symbol.Mine.symbol) {
                minesAround = true
                break
            }
        }
    }
    return minesAround
}

private fun determineRange(num1: Int, num2: Int) =
    when (num1) {
        0 -> 0..1
        num2 -> num1 - 1..num2
        else -> num1 - 1..num1 + 1
    }

当前9x9棋盘状态

|123456789|
-|---------|
1|.........|
2|.........|
3|.........|
4|.........|
5|.........|
6|.........|
7|.........|
8|.........|
9|.........|
-|---------|

错误原因分析

1. 核心错误:函数参数传递顺序颠倒

explore函数定义为explore(column: Int, row: Int),第一个参数是列索引,第二个是行索引。但循环中调用explore(rowNumber, columnNumber)时,把行索引当成列参数、列索引当成行参数,导致每次递归都访问错位的单元格:

  • 原本要探索(行rowNumber, 列columnNumber),实际操作的是displayedMinefield[columnNumber][rowNumber]这个完全错误的位置。
  • 这种错位会让递归在两组错误的单元格之间无限循环(比如反复调用(0,1)和(1,0)对应的错位位置),最终触发栈溢出。

2. 次要错误:已探索标记时机错误

处理周边单元格时,你先将其设置为minefield的空符号,再递归调用explore。但空符号不是已探索标记,其他递归分支访问该单元格时会认为它未被处理,重复进入递归,加剧栈溢出风险。


修复方案

1. 修正参数传递顺序

将循环中的explore(rowNumber, columnNumber)改为explore(columnNumber, rowNumber),确保列、行参数顺序与函数定义一致。

2. 调整标记逻辑

在explore函数开头先判断是否已探索,避免重复递归;同时先标记为已探索,再处理周边单元格:

修复后的explore函数:

private fun explore(column: Int, row: Int) {
    // 已探索过直接返回,避免重复递归
    if (displayedMinefield[row][column] == Symbol.FCell.symbol) return
    
    displayedMinefield[row][column] = Symbol.FCell.symbol
    
    // 周围有地雷则停止递归
    if (checkIfMinesAround(row, column)) return
    
    // 遍历周边所有单元格并递归探索
    for (rowNumber in determineRange(row, minefield.lastIndex)) {
        for (columnNumber in determineRange(column, minefield[rowNumber].lastIndex)) {
            explore(columnNumber, rowNumber)
        }
    }
}

3. 优化地雷检查逻辑

checkIfMinesAround可以跳过当前单元格(已确认无雷),并找到地雷后立即返回,提升效率:

private fun checkIfMinesAround(row: Int, column: Int): Boolean {
    for (rowNumber in determineRange(row, minefield.lastIndex)) {
        for (columnNumber in determineRange(column, minefield[rowNumber].lastIndex)) {
            // 跳过当前单元格,减少不必要检查
            if (rowNumber == row && columnNumber == column) continue
            if (minefield[rowNumber][columnNumber] == Symbol.Mine.symbol) {
                return true // 找到地雷立即返回
            }
        }
    }
    return false
}

内容的提问来源于stack exchange,提问作者anirudh30three

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 19:18:19