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

Love2D三消游戏出现Stack Overflow错误,求解决方法

解决Love2D瓷砖匹配游戏的栈溢出问题

问题背景

使用Love2D开发瓷砖匹配消除游戏时,触发stack overflow错误,错误栈显示matches、checkforNil、removeMatches三个函数陷入无限递归调用。

错误栈信息

main.lua:147: stack overflow

Traceback

[love "callbacks.lua"]:228: in function 'handler'
main.lua:94: in function 'matches'
main.lua:80: in function 'checkforNil'
main.lua:89: in function 'removeMatches'
main.lua:148: in function 'matches'
main.lua:80: in function 'checkforNil'
main.lua:89: in function 'removeMatches'
main.lua:148: in function 'matches'
main.lua:80: in function 'checkforNil'
main.lua:89: in function 'removeMatches'
...
main.lua:89: in function 'removeMatches'
main.lua:148: in function 'matches'
main.lua:80: in function 'checkforNil'
main.lua:89: in function 'removeMatches'
main.lua:148: in function 'matches'
main.lua:161: in function 'swap'
main.lua:183: in function <main.lua:175>
[love "callbacks.lua"]:154: in function <[love "callbacks.lua"]:144>
[C]: in function 'xpcall'

可复现代码

local game = {}

-- Table to store the grid elements
gridTable = {}
local selectedTile = nil

-- Function to initialize the gridTable with colors
function game:initializeGrid()
    for i = 1, 64 do
        gridTable[i] = {
            x = ((i - 1) % 8) * 65,
            y = (math.floor((i - 1) / 8) * 65),
            color = love.math.random(7)
        }
    end
end

-- Function to draw the grid elements
function game:drawGrid()
    for i, cell in ipairs(gridTable) do
        if cell.color then
            love.graphics.setColor(self:getColor(cell.color))
            love.graphics.rectangle("fill", cell.x, cell.y, 50, 50)
        end
    end
    if selectedTile then
        love.graphics.setColor(1, 1, 1)
        love.graphics.rectangle("line", gridTable[selectedTile].x, gridTable[selectedTile].y, 50, 50)
    end
end

-- Helper function to get color by index
function game:getColor(index)
    local colors = {
        {1, 0, 0},
        {0, 1, 0},
        {0, 0, 1},
        {1, 1, 0},
        {0, 1, 1},
        {1, 0, 1},
        {0.5, 0.5, 0.5}
    }
    return colors[index]
end

function game:checkforNil() -- looks for nil values in the table
    for i = 64, 56, -1 do --grid is 8x8 so 64 items
      local counter = 8 -- looks through each row
      for j = i, 1, -8 do
        local tempY, tempColor = 0, 0

        if gridTable[j].y == nil then -- if value at j is nil we swap the nil value with what's about it and repeats until all values are filled
          for test = j, 1, -8 do
            if gridTable[test].y ~= nil then
              tempY = gridTable[test].y
                tempColor = gridTable[test].color

                gridTable[j].y = ((counter) * 50) + (((counter) - 1) * 15)
                gridTable[j].color = tempColor

                gridTable[test].y = nil
                gridTable[test].color = nil
                break
                end
                end
            end

            for k = 1, 8 do -- loops through the top and assigns random colours
                if gridTable[k].y == nil then
                    gridTable[k].y = 50
                    gridTable[k].color = love.math.random(7)
                end
            end

            counter = counter - 1
        end
    end
    self:matches(gridTable)
end

function game:removeMatches(grid)
    for _, v in ipairs(listofMatches) do --lists of matches contain where in the table there are 3 or more colours of the same 
        grid[v].y = nil --and removes them
        grid[v].color = nil
    end
    listofMatches = {} --resets the list 
    self:checkforNil() -- calls the function that pushes bricks down
end

function game:matches(grid) --searches everytime we swap for a match
    matchTest = false --if we find at least one match we'll set this to true
    listofMatches = {}
    local matches = 0 

    for i = 1, 63 do --loops through the table of which there are 64 items
      for _, y in ipairs(listofMatches) do
        if i == y then
          goto continue --if i and i+1 are a match we skip to i+2 to see if that matches too
        end
      end
      ::continue::
      for j = i + 1, 64 do --in this loop we are looking for horizontal matches
        local currentColor = grid[i].color --sets the current colour we are searching for
          if currentColor ~= grid[j].color then --we break out of searching for the same colour and if the
            if matches > 1 then --matches are 3+ we set match test to tru and insert into list of matches
              matchTest = true
              for k = i, j - 1 do
                table.insert(listofMatches, k)
              end
            end
            matches = 0
            break
          else
          matches = matches + 1
          end
        end
    end

    for num = 1, 8 do --looking for vertical
      for vertical = num, 48, 8 do
        for _, y in ipairs(listofMatches) do
          if vertical == y then
            goto continue
            end
          end
          ::continue::
          for down = vertical + 8, 64, 8 do
            local currentColor = grid[vertical].color
            if currentColor ~= grid[down].color then
            if matches > 1 then
              matchTest = true
              for k = vertical, down - 8, 8 do
                table.insert(listofMatches, k)
              end
              end
              matches = 0
              break
            else
              matches = matches + 1
            end
          end
        end
    end

    if matchTest then --if there is a match test we want to remove matches so call the function and add points
        self:removeMatches(grid)
        game.points = (game.points or 0) + 500
        matchTest = false
    end
end

function game:swap(swap1, swap2) --swapping blocks and then checking if there's a match
    local temp1 = gridTable[swap1].color
    local temp2 = gridTable[swap2].color

    gridTable[swap1].color = temp2
    gridTable[swap2].color = temp1

    self:matches(gridTable)
end

-- Love2D callback functions
function love.load()
    game:initializeGrid()
end

function love.draw()
    game:drawGrid()
    love.graphics.setColor(1, 1, 1)
    love.graphics.print("Points: " .. (game.points or 0), 10, 10)
end

function love.mousepressed(x, y, button)
    if button == 1 then
        local tileIndex = game:getTileIndex(x, y)
        if tileIndex then
            if not selectedTile then
                selectedTile = tileIndex
            else
                if selectedTile ~= tileIndex then
                    game:swap(selectedTile, tileIndex)
                    selectedTile = nil
                else
                    selectedTile = nil
                end
            end
        end
    end
end

function game:getTileIndex(x, y)
    for i, cell in ipairs(gridTable) do
        if x > cell.x and x < cell.x + 50 and y > cell.y and y < cell.y + 50 then
            return i
        end
    end
    return nil
end

问题分析

栈溢出的核心原因是无限递归循环:

  1. matches检测到匹配后调用removeMatches
  2. removeMatches处理完成后调用checkforNil填补空位
  3. checkforNil结束后立即调用matches再次检测
  4. 新生成的瓷砖可能再次触发匹配,导致上述流程无限重复,直到栈内存耗尽

此外还有两个次要问题:

  • 垂直匹配检测时未重置matches计数器,可能导致错误的匹配判定
  • listofMatches和matchTest为全局变量,容易引发状态混乱

解决方案

1. 打破无限递归循环

将后续匹配检测移到Love2D的帧循环love.update中执行,避免瞬间填满调用栈。

修改checkforNil函数,移除末尾的self:matches(gridTable),添加需要检测匹配的标记:

function game:checkforNil() -- looks for nil values in the table
    for i = 64, 56, -1 do --grid is 8x8 so 64 items
      local counter = 8 -- looks through each row
      for j = i, 1, -8 do
        local tempY, tempColor = 0, 0

        if gridTable[j].y == nil then -- if value at j is nil we swap the nil value with what's about it and repeats until all values are filled
          for test = j, 1, -8 do
            if gridTable[test].y ~= nil then
              tempY = gridTable[test].y
                tempColor = gridTable[test].color

                gridTable[j].y = ((counter) * 50) + (((counter) - 1) * 15)
                gridTable[j].color = tempColor

                gridTable[test].y = nil
                gridTable[test].color = nil
                break
                end
                end
            end

            for k = 1, 8 do -- loops through the top and assigns random colours
                if gridTable[k].y == nil then
                    gridTable[k].y = 50
                    gridTable[k].color = love.math.random(7)
                end
            end

            counter = counter - 1
        end
    end
    -- 标记需要在帧循环中检查匹配
    game.needsMatchCheck = true
end

添加love.update回调处理匹配检测:

function love.update(dt)
    if game.needsMatchCheck then
        game.needsMatchCheck = false
        game:matches(gridTable)
    end
end

2. 修复垂直匹配计数器问题

在垂直匹配检测的外层循环中,每次开始新列检测前重置matches计数器:

for num = 1, 8 do --looking for vertical
  matches = 0 -- 重置计数器
  for vertical = num, 48, 8 do
    for _, y in ipairs(listofMatches) do
      if vertical == y then
        goto continue
        end
      end
      ::continue::
      for down = vertical + 8, 64, 8 do
        local currentColor = grid[vertical].color
        if currentColor ~= grid[down].color then
        if matches > 1 then
          matchTest = true
          for k = vertical, down - 8, 8 do
            table.insert(listofMatches, k)
          end
          end
          matches = 0
          break
        else
          matches = matches + 1
        end
      end
    end
end

3. 全局变量改为局部变量

将listofMatches和matchTest移到matches函数内部,避免全局状态污染:

function game:matches(grid) --searches everytime we swap for a match
    local matchTest = false -- 改为局部变量
    local listofMatches = {} -- 改为局部变量
    local matches = 0 

    -- 原有循环逻辑保持不变
    ...

    if matchTest then
        -- 将匹配列表作为参数传递给removeMatches
        self:removeMatches(grid, listofMatches)
        game.points = (game.points or 0) + 500
    end
end

修改removeMatches函数接收参数:

function game:removeMatches(grid, listofMatches)
    for _, v in ipairs(listofMatches) do
        grid[v].y = nil
        grid[v].color = nil
    end
    self:checkforNil()
end

4. 可选:递归深度限制(备用方案)

如果坚持使用递归,可添加深度限制防止无限递归:

function game:matches(grid, depth)
    depth = depth or 0
    if depth > 10 then -- 限制最多递归10层
        return
    end
    -- 原有代码
    ...
    if matchTest then
        self:removeMatches(grid, listofMatches)
        game.points = (game.points or 0) + 500
        self:matches(grid, depth + 1)
    end
end

但更推荐使用帧循环方案,符合Love2D的游戏设计模式。

总结

核心问题是无限递归调用,通过将后续匹配检测移到love.update帧循环中可彻底解决栈溢出。同时修复计数器和变量作用域问题,能提升代码稳定性与可维护性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:45:53