using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; using ChillConnect; namespace ChillConnect.Editor { public static class ArrowLevelGenerator { private const int MaxGenerateAttempts = 500; // 单关最大尝试次数 private static readonly string[] Directions = { "up", "down", "left", "right" }; /// /// 生成单个关卡 /// public static EditorLevelConfig GenerateLevel(int levelId, LevelDifficultyPreset preset) { for (int attempt = 0; attempt < MaxGenerateAttempts; attempt++) { EditorLevelConfig level = new EditorLevelConfig { levelId = levelId, levelName = $"第{levelId - 1000}关", gridRows = preset.gridRows, gridCols = preset.gridCols, pointSpacing = 60, arrows = new List() }; bool[,] occupied = new bool[preset.gridRows, preset.gridCols]; int arrowId = 1; int failedCount = 0; // 逐个生成箭头,直到达到目标数量或多次失败 while (level.arrows.Count < preset.arrowCount && failedCount < 100) { if (TryGenerateArrow(preset, occupied, level.gridRows, level.gridCols, out EditorArrowConfig arrow)) { arrow.id = arrowId++; level.arrows.Add(arrow); // 标记占用 MarkOccupied(arrow, occupied, level.gridCols); } else { failedCount++; } } // 箭头数太少,放弃本次 if (level.arrows.Count < preset.minArrowCount) continue; // 可解性校验 if (CheckSolvable(level)) { return level; } } Debug.LogWarning($"关卡 {levelId} 达到最大尝试次数仍未生成合格关卡"); return null; } /// /// 尝试生成单个合法箭头 /// private static bool TryGenerateArrow(LevelDifficultyPreset preset, bool[,] occupied, int rows, int cols, out EditorArrowConfig arrow) { arrow = null; // 随机起点 int startRow = Random.Range(0, rows); int startCol = Random.Range(0, cols); if (occupied[startRow, startCol]) return false; // 随机路径长度和折数 int pathLength = Random.Range(preset.minPathLength, preset.maxPathLength + 1); int maxTurns = preset.maxTurnCount; int turns = 0; List path = new List(); List<(int r, int c)> pathPoints = new List<(int r, int c)> { (startRow, startCol) }; string lastDir = Directions[Random.Range(0, Directions.Length)]; int curRow = startRow; int curCol = startCol; for (int i = 0; i < pathLength; i++) { string curDir = lastDir; // 有概率转向 if (i > 0 && turns < maxTurns && Random.value < preset.turnProbability) { // 随机换一个方向(不能回头) List validDirs = new List(Directions); validDirs.Remove(GetOppositeDir(lastDir)); curDir = validDirs[Random.Range(0, validDirs.Count)]; turns++; } // 计算下一点 (int nr, int nc) = MoveDir(curRow, curCol, curDir); // 越界或已占用,终止 if (nr < 0 || nr >= rows || nc < 0 || nc >= cols || occupied[nr, nc]) break; path.Add(curDir); pathPoints.Add((nr, nc)); curRow = nr; curCol = nc; lastDir = curDir; } // 路径太短,无效 if (path.Count < 1) return false; // 构造箭头 arrow = new EditorArrowConfig { startPoint = startRow * cols + startCol, path = path }; return true; } /// /// 标记箭头占用的网格点 /// private static void MarkOccupied(EditorArrowConfig arrow, bool[,] occupied, int totalCols) { int pointId = arrow.startPoint; int row = pointId / totalCols; int col = pointId % totalCols; occupied[row, col] = true; foreach (string dir in arrow.path) { (row, col) = MoveDir(row, col, dir); occupied[row, col] = true; } } /// /// 可解性校验:贪心模拟消除法 /// public static bool CheckSolvable(EditorLevelConfig level) { int rows = level.gridRows; int cols = level.gridCols; // 复制占用矩阵 bool[,] occupied = new bool[rows, cols]; foreach (var arrow in level.arrows) { MarkOccupied(arrow, occupied, cols); } List remaining = new List(level.arrows); int roundCount = 0; const int maxRounds = 200; while (remaining.Count > 0 && roundCount < maxRounds) { roundCount++; List canRemove = new List(); foreach (var arrow in remaining) { if (CanArrowFlyOut(arrow, occupied, rows, cols)) { canRemove.Add(arrow); } } if (canRemove.Count == 0) return false; // 死局 // 消除第一个可消除的箭头,释放占用 var toRemove = canRemove[0]; ReleaseOccupied(toRemove, occupied, cols); remaining.Remove(toRemove); } return remaining.Count == 0; } /// /// 判断箭头是否能直接飞出(前方无阻挡) /// private static bool CanArrowFlyOut(EditorArrowConfig arrow, bool[,] occupied, int rows, int cols) { // 获取最终射出方向 string finalDir = arrow.path[arrow.path.Count - 1]; // 获取箭头头部位置(路径终点) int pointId = arrow.startPoint; int row = pointId / cols; int col = pointId % cols; foreach (string dir in arrow.path) { (row, col) = MoveDir(row, col, dir); } // 沿射出方向一路检查到边界 while (true) { (row, col) = MoveDir(row, col, finalDir); if (row < 0 || row >= rows || col < 0 || col >= cols) return true; // 飞出边界 if (occupied[row, col]) return false; // 前方有阻挡 } } /// /// 释放箭头占用 /// private static void ReleaseOccupied(EditorArrowConfig arrow, bool[,] occupied, int totalCols) { int pointId = arrow.startPoint; int row = pointId / totalCols; int col = pointId % totalCols; occupied[row, col] = false; foreach (string dir in arrow.path) { (row, col) = MoveDir(row, col, dir); occupied[row, col] = false; } } #region 工具方法 private static (int r, int c) MoveDir(int row, int col, string dir) { switch (dir) { case "up": return (row - 1, col); case "down": return (row + 1, col); case "left": return (row, col - 1); case "right": return (row, col + 1); default: return (row, col); } } private static string GetOppositeDir(string dir) { switch (dir) { case "up": return "down"; case "down": return "up"; case "left": return "right"; case "right": return "left"; default: return dir; } } #endregion } }