#if UNITY_EDITOR using System.Collections.Generic; using System.IO; using System.Linq; using ChillConnect; using UnityEditor; using UnityEngine; /// /// 关卡死局检测工具(Unity编辑器下使用) /// public class ArrowGameValidator { // ========== 方向转换工具 ========== /// /// 方向字符串转行列偏移量 /// private static (int dRow, int dCol) DirToOffset(string dir) { return dir switch { "up" => (-1, 0), "down" => (1, 0), "left" => (0, -1), "right" => (0, 1), _ => (0, 0) }; } /// /// pointId 转 (row, col) /// private static (int row, int col) PointIdToRowCol(int pointId, int totalCols) { int row = pointId / totalCols; int col = pointId % totalCols; return (row, col); } /// /// (row, col) 转 pointId /// private static int RowColToPointId(int row, int col, int totalCols) { return row * totalCols + col; } // ========== 核心检测逻辑 ========== /// /// 构建所有箭头占用的网格点集合 /// 返回: Dictionary> —— 每个网格点被哪些箭头占用 /// private static Dictionary> BuildGridOccupation(LevelConfig config) { var gridOccupation = new Dictionary>(); int totalCols = config.gridCols; foreach (var arrow in config.arrows) { int curPointId = arrow.startPoint; // 起点也加入占用 if (!gridOccupation.ContainsKey(curPointId)) gridOccupation[curPointId] = new List(); gridOccupation[curPointId].Add(arrow.id); // 沿路径逐步添加占用 foreach (string dir in arrow.path) { var (row, col) = PointIdToRowCol(curPointId, totalCols); var (dRow, dCol) = DirToOffset(dir); row += dRow; col += dCol; // 边界检查 if (row < 0 || row >= config.gridRows || col < 0 || col >= config.gridCols) { Debug.LogWarning($"箭头 {arrow.id} 路径超出网格边界"); break; } curPointId = RowColToPointId(row, col, totalCols); if (!gridOccupation.ContainsKey(curPointId)) gridOccupation[curPointId] = new List(); gridOccupation[curPointId].Add(arrow.id); } } return gridOccupation; } /// /// 获取箭头的最终方向(头部朝向) /// private static string GetFinalDirection(ArrowConfig arrow) { if (arrow.path != null && arrow.path.Count > 0) return arrow.path[arrow.path.Count - 1]; return "up"; // 默认向上 } /// /// 获取箭头头部的网格位置 (pointId) /// private static int GetArrowHeadPointId(ArrowConfig arrow, int totalCols) { int curPointId = arrow.startPoint; foreach (string dir in arrow.path) { var (row, col) = PointIdToRowCol(curPointId, totalCols); var (dRow, dCol) = DirToOffset(dir); curPointId = RowColToPointId(row + dRow, col + dCol, totalCols); } return curPointId; } /// /// 构建依赖图:检测每个箭头前方被哪些其他箭头阻挡 /// 返回: 邻接表 —— blockedBy[arrowId] = List of arrowIds that block it /// 即:要消除 arrowId,必须先消除 blockedBy[arrowId] 中的箭头 /// private static Dictionary> BuildDependencyGraph( LevelConfig config, Dictionary> gridOccupation) { var blockedBy = new Dictionary>(); int totalCols = config.gridCols; int totalRows = config.gridRows; // 初始化 foreach (var arrow in config.arrows) { blockedBy[arrow.id] = new HashSet(); } foreach (var arrow in config.arrows) { string finalDir = GetFinalDirection(arrow); int headPointId = GetArrowHeadPointId(arrow, totalCols); var (headRow, headCol) = PointIdToRowCol(headPointId, totalCols); var (dRow, dCol) = DirToOffset(finalDir); // 沿最终方向逐格扫描 int curRow = headRow + dRow; int curCol = headCol + dCol; while (curRow >= 0 && curRow < totalRows && curCol >= 0 && curCol < totalCols) { int curPointId = RowColToPointId(curRow, curCol, totalCols); // 检查该网格点是否被其他箭头占用 if (gridOccupation.TryGetValue(curPointId, out var occupyingArrows)) { foreach (int occupyingArrowId in occupyingArrows) { // 不能阻挡自己 if (occupyingArrowId != arrow.id) { // occupyingArrow 阻挡了当前箭头 // 要消除当前箭头,必须先消除 occupyingArrow blockedBy[arrow.id].Add(occupyingArrowId); } } } curRow += dRow; curCol += dCol; } } return blockedBy; } /// /// 使用拓扑排序检测是否有环(死局) /// 返回: (isSolvable, unsolvableArrowIds) /// private static (bool isSolvable, List unsolvableArrowIds) DetectDeadlock( LevelConfig config, Dictionary> blockedBy) { // 计算每个箭头的入度(被多少箭头阻挡) var inDegree = new Dictionary(); foreach (var arrow in config.arrows) { inDegree[arrow.id] = 0; } // 构建反向邻接表:谁依赖于我 var dependents = new Dictionary>(); foreach (var arrow in config.arrows) { dependents[arrow.id] = new List(); } foreach (var kvp in blockedBy) { int arrowId = kvp.Key; foreach (int blockerId in kvp.Value) { // arrowId 依赖 blockerId // blockerId -> arrowId dependents[blockerId].Add(arrowId); inDegree[arrowId]++; } } // Kahn 拓扑排序 var queue = new Queue(); foreach (var arrow in config.arrows) { if (inDegree[arrow.id] == 0) { queue.Enqueue(arrow.id); } } int removedCount = 0; while (queue.Count > 0) { int current = queue.Dequeue(); removedCount++; foreach (int dependent in dependents[current]) { inDegree[dependent]--; if (inDegree[dependent] == 0) { queue.Enqueue(dependent); } } } // 如果所有箭头都能被移除,则可解 if (removedCount == config.arrows.Count) { return (true, new List()); } // 找出无法移除的箭头(入度 > 0 的) var unsolvableArrows = new List(); foreach (var arrow in config.arrows) { if (inDegree[arrow.id] > 0) { unsolvableArrows.Add(arrow.id); } } return (false, unsolvableArrows); } /// /// 从环路中提取所有涉及的箭头ID /// private static List ExtractCycleArrowIds( LevelConfig config, Dictionary> blockedBy, List unsolvableArrows) { // 从不可解的箭头出发,DFS找出所有在环路中的箭头 var allCycleNodes = new HashSet(); foreach (int startId in unsolvableArrows) { if (allCycleNodes.Contains(startId)) continue; // 尝试从 startId 出发找环 var visited = new HashSet(); var inStack = new HashSet(); var path = new List(); if (FindCycle(startId, blockedBy, visited, inStack, path, out var cycleNodes)) { foreach (int node in cycleNodes) { allCycleNodes.Add(node); } } } return allCycleNodes.ToList(); } /// /// DFS寻找环路 /// private static bool FindCycle( int current, Dictionary> blockedBy, HashSet visited, HashSet inStack, List path, out List cycleNodes) { visited.Add(current); inStack.Add(current); path.Add(current); if (blockedBy.TryGetValue(current, out var blockers)) { foreach (int blocker in blockers) { if (!visited.Contains(blocker)) { if (FindCycle(blocker, blockedBy, visited, inStack, path, out cycleNodes)) { inStack.Remove(current); path.RemoveAt(path.Count - 1); return true; } } else if (inStack.Contains(blocker)) { // 找到环 int cycleStartIndex = path.IndexOf(blocker); cycleNodes = path.GetRange(cycleStartIndex, path.Count - cycleStartIndex); inStack.Remove(current); path.RemoveAt(path.Count - 1); return true; } } } inStack.Remove(current); path.RemoveAt(path.Count - 1); cycleNodes = null; return false; } // ========== 公共接口 ========== /// /// 检测单个关卡是否可解 /// public static ValidationResult ValidateLevel(LevelConfig config) { var result = new ValidationResult { levelId = config.levelId, levelName = config.levelName }; if (config.arrows == null || config.arrows.Count == 0) { result.isSolvable = true; result.message = "关卡无箭头"; return result; } // 1. 构建网格占用 var gridOccupation = BuildGridOccupation(config); // 2. 构建依赖图 var blockedBy = BuildDependencyGraph(config, gridOccupation); // 3. 拓扑排序检测 var (isSolvable, unsolvableArrows) = DetectDeadlock(config, blockedBy); result.isSolvable = isSolvable; result.blockedBy = blockedBy; if (isSolvable) { result.message = "✅ 可解"; } else { // 找出环路中的具体箭头 var cycleArrows = ExtractCycleArrowIds(config, blockedBy, unsolvableArrows); result.unsolvableArrowIds = cycleArrows.OrderBy(x => x).ToList(); result.message = $"❌ 死局,无法消除的箭头: [{string.Join(", ", result.unsolvableArrowIds)}]"; } return result; } /// /// 批量检测关卡目录下所有JSON文件 /// public static void ValidateAllLevels(string folderPath) { if (!Directory.Exists(folderPath)) { Debug.LogError($"目录不存在: {folderPath}"); return; } string[] jsonFiles = Directory.GetFiles(folderPath, "*.json"); int totalCount = jsonFiles.Length; int solvableCount = 0; int unsolvableCount = 0; var unsolvableResults = new List(); foreach (string filePath in jsonFiles) { try { string json = File.ReadAllText(filePath); LevelConfig config = JsonUtility.FromJson(json); var result = ValidateLevel(config); if (result.isSolvable) solvableCount++; else { unsolvableCount++; unsolvableResults.Add(result); } Debug.Log($"[{filePath}] {result.message}"); } catch (System.Exception e) { Debug.LogError($"解析文件失败: {filePath}, 错误: {e.Message}"); } } Debug.Log("========== 检测汇总 =========="); Debug.Log($"总关卡数: {totalCount}"); Debug.Log($"可解关卡: {solvableCount}"); Debug.Log($"死局关卡: {unsolvableCount}"); if (unsolvableResults.Count > 0) { Debug.Log("========== 死局关卡详情 =========="); foreach (var result in unsolvableResults) { Debug.Log($"关卡 {result.levelId} ({result.levelName}): {result.message}"); } } } // ========== 编辑器菜单 ========== [MenuItem("Tools/箭头游戏/检测单个关卡(选择JSON文件)")] public static void ValidateSelectedLevel() { string path = EditorUtility.OpenFilePanel("选择关卡JSON", Application.streamingAssetsPath, "json"); if (string.IsNullOrEmpty(path)) return; try { string json = File.ReadAllText(path); LevelConfig config = JsonUtility.FromJson(json); var result = ValidateLevel(config); Debug.Log($"========== 关卡 {result.levelId} ({result.levelName}) 检测结果 =========="); Debug.Log(result.message); if (!result.isSolvable) { Debug.Log($"环路箭头: [{string.Join(", ", result.unsolvableArrowIds)}]"); // 打印依赖详情 Debug.Log("========== 依赖关系 =========="); foreach (int id in result.unsolvableArrowIds) { if (result.blockedBy.TryGetValue(id, out var blockers)) { Debug.Log($"箭头 {id} 被阻挡于: [{string.Join(", ", blockers)}]"); } } } EditorUtility.DisplayDialog("检测完成", result.message, "确定"); } catch (System.Exception e) { Debug.LogError($"检测失败: {e.Message}"); EditorUtility.DisplayDialog("错误", $"检测失败: {e.Message}", "确定"); } } [MenuItem("Tools/箭头游戏/批量检测所有关卡")] public static void ValidateAllLevelsMenu() { string folderPath = EditorUtility.OpenFolderPanel("选择关卡目录", Application.streamingAssetsPath, ""); if (string.IsNullOrEmpty(folderPath)) return; ValidateAllLevels(folderPath); EditorUtility.DisplayDialog("批量检测完成", "请查看Console输出结果", "确定"); } // ========== 数据结构 ========== /// /// 检测结果 /// public class ValidationResult { public int levelId; public string levelName; public bool isSolvable; public string message; public List unsolvableArrowIds = new List(); public Dictionary> blockedBy; } } #endif