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

PHP递归函数嵌套while失效,能否用循环实现瘟疫桌游爆发逻辑?

解决《瘟疫危机》爆发逻辑的递归问题与非递归实现

嘿,我来帮你排查递归失效的问题,同时给你一个基于循环的非递归实现方案!

先说说你递归代码的问题

你的递归逻辑失效,大概率不是因为嵌套在while循环里,而是这几个关键点没处理好:

  • 缺少「同一次连锁爆发中城市仅爆发一次」的限制
    瘟疫危机的规则里,同一个城市在一轮连锁爆发中只能触发一次爆发(不然会无限循环,比如A连B、B连A的情况,爆发后会互相反复触发)。你的代码里没有标记哪些城市已经在本次连锁中处理过,递归调用时会反复触发同一城市的爆发,导致逻辑混乱甚至死循环。
  • INSERT语句的逻辑隐患
    你先尝试INSERT新记录,如果失败才去更新已有记录。但递归调用时,连接城市大概率已经有记录,INSERT会失败进入更新逻辑,若没开启mysqli错误报告,可能隐藏执行错误。另外这种先插后更的写法不够严谨,建议用INSERT ... ON DUPLICATE KEY UPDATE替代,更高效可靠。
  • 没有处理事务一致性
    爆发是连锁原子操作,若不用数据库事务包裹整个流程,可能出现部分城市更新成功、部分失败的情况,导致游戏状态不一致。

非递归实现方案(队列+while循环)

不用递归的话,我们可以用队列模拟连锁爆发流程,更直观也更容易调试控制。核心思路是:把需要处理的城市加入队列,逐个取出处理;若触发爆发,就把未处理过的连接城市加入队列,直到队列为空。

完整代码示例

function AddInfectionCubes($target_city, $initial_cubes, $game_id) {
    // 开启事务,保证爆发流程原子性
    mysqli_begin_transaction($conn);
    
    try {
        // 记录本次连锁中已处理的城市,避免重复触发
        $processed_cities = [];
        // 队列存储待处理的城市与要添加的立方体数量
        $queue = [[$target_city, $initial_cubes]];
        
        while (!empty($queue)) {
            // 取出队列头部的任务
            list($city_name, $n_cubes) = array_shift($queue);
            
            // 跳过已处理过的城市
            if (in_array($city_name, $processed_cities)) {
                continue;
            }
            $processed_cities[] = $city_name;
            
            // 获取当前城市的立方体数量(无记录则默认0)
            $sql_get = "SELECT disease_cubes FROM game_city WHERE city_name = ? AND id_game = ?";
            $stmt = mysqli_prepare($conn, $sql_get);
            mysqli_stmt_bind_param($stmt, "si", $city_name, $game_id);
            mysqli_stmt_execute($stmt);
            $result = mysqli_stmt_get_result($stmt);
            $current_cubes = 0;
            if ($row = mysqli_fetch_assoc($result)) {
                $current_cubes = $row['disease_cubes'];
            }
            mysqli_stmt_close($stmt);
            
            // 计算添加后的总立方体数
            $new_cubes = $current_cubes + $n_cubes;
            
            if ($new_cubes > 3) {
                // 触发爆发:城市最多保留3个立方体
                $final_cubes = 3;
                // 更新爆发标记
                $stmt = mysqli_prepare($conn, "UPDATE Game SET outbreak_marker = outbreak_marker + 1 WHERE id = ?");
                mysqli_stmt_bind_param($stmt, "i", $game_id);
                mysqli_stmt_execute($stmt);
                mysqli_stmt_close($stmt);
                
                // 获取当前城市的所有连接城市
                $stmt = mysqli_prepare($conn, "SELECT city_to FROM connections WHERE city_from = ?");
                mysqli_stmt_bind_param($stmt, "s", $city_name);
                mysqli_stmt_execute($stmt);
                $result = mysqli_stmt_get_result($stmt);
                
                // 将未处理的连接城市加入队列,每个添加1个立方体
                while ($row = mysqli_fetch_assoc($result)) {
                    $connected_city = $row['city_to'];
                    if (!in_array($connected_city, $processed_cities)) {
                        $queue[] = [$connected_city, 1];
                    }
                }
                mysqli_stmt_close($stmt);
            } else {
                // 未触发爆发,直接设置新的立方体数
                $final_cubes = $new_cubes;
            }
            
            // 插入或更新城市记录(用ON DUPLICATE KEY UPDATE保证原子性)
            $stmt = mysqli_prepare($conn, "
                INSERT INTO game_city(id_game, city_name, disease_cubes)
                VALUES(?, ?, ?)
                ON DUPLICATE KEY UPDATE disease_cubes = ?
            ");
            mysqli_stmt_bind_param($stmt, "isii", $game_id, $city_name, $final_cubes, $final_cubes);
            mysqli_stmt_execute($stmt);
            mysqli_stmt_close($stmt);
        }
        
        // 所有操作完成,提交事务
        mysqli_commit($conn);
    } catch (Exception $e) {
        // 出错回滚事务
        mysqli_rollback($conn);
        error_log("Infection cube error: " . $e->getMessage());
        throw $e;
    }
}

// 调用示例
// AddInfectionCubes("Atlanta", 2, 1);

这个方案的优势

  • 避免栈溢出:深层连锁爆发时递归可能触发PHP栈溢出,队列+循环无此问题。
  • 易调试:可随时打印队列和已处理城市状态,快速排查问题。
  • 状态一致:事务保证整个爆发流程要么全部成功,要么全部回滚,不会出现游戏状态错乱。
  • 符合规则:严格限制同一连锁中每个城市仅处理一次,避免无限循环。

额外优化建议

  • 加联合唯一索引:给game_city表的id_game和city_name加联合唯一索引,确保INSERT ... ON DUPLICATE KEY UPDATE正常工作。
  • 限制爆发次数:瘟疫危机中爆发次数达阈值会游戏失败,可在代码中添加判断,达到阈值时终止游戏逻辑。
  • 持续使用预处理语句:避免SQL注入,同时提升数据库执行效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:14:20