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
相关产品推荐
相关产品推荐

