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
}
}