using System; using System.Collections.Generic; using System.Linq; internal static class MaskLibrary { public static readonly List Microban = new() { new[]{ "0000000", "0####00", "0#..#00", "0#..###", "0#....#", "0######", "0000000" }, new[]{ "0000000", "0#####0", "0#...#0", "0#.#.#0", "0#...#0", "0#####0", "0000000" }, new[]{ "0000000", "0###000", "0#.#000", "0#.#000", "0#..###", "0#####0", "0000000" }, new[]{ "00000000", "0######0", "0#....#0", "0###..#0", "000#..#0", "000####0", "00000000" }, new[]{ "000000000", "00#####00", "00#...#00", "0##...##0", "0#.....#0", "0##...##0", "00#...#00", "00#####00", "000000000" }, new[]{ "00000000", "0######0", "0#....#0", "0#.####0", "0#....#0", "0###..#0", "000####0", "00000000" }, new[]{ "00000000", "0####000", "0#..###0", "0#....#0", "0###..#0", "000####0", "00000000" }, new[]{ "0000000", "0#####0", "0#...#0", "0#...#0", "0#...#0", "0###.#0", "000###0", "0000000" }, new[]{ "0000000", "0####00", "0#..#00", "0#..###", "0#..#.#", "0####.#", "000000#" }, new[]{ "00000000", "0######0", "0#....#0", "0#.#..#0", "0#.#.##0", "0#...#00", "0#####00", "00000000" }, new[]{ "0000000", "0#####0", "0#...#0", "###.###", "0#...#0", "0#...#0", "0#####0" }, new[]{ "0000000000", "00#######0", "00#.....#0", "###.###.#0", "0#.....#0", "0#######0", "000000000" }, // 13: 얇은 리본형 new[]{ "000000000", "00####000", "00#..###0", "0##..#.#0", "0#....#00", "0##..###0", "00####000", "000000000" }, // 14: 작은 도넛 new[]{ "0000000", "0#####0", "0#...#0", "0#.#.#0", "0#...#0", "0#####0", "0000000" }, // 15: ㄴ자 계단형 new[]{ "00000000", "0####000", "0#..###0", "0#....#0", "0###..#0", "000#..#0", "000####0", "00000000" }, // 16: 짧은 S자 복도 new[]{ "00000000", "0####000", "0#..###0", "0#..#.#0", "0###..#0", "000####0", "00000000" }, // 17: 코너 방 + 넓은 홀 new[]{ "000000000", "00#####00", "00#...#00", "0##.#.##0", "0#.....#0", "0#.#.###0", "0#...#000", "0#####000", "000000000" }, // 18: 긴 복도 + 옆 포켓 new[]{ "0000000000", "00#######0", "00#.....#0", "0##.###.#0", "0#.....#00", "0#.#.###00", "0#.....#00", "0#######00", "0000000000" }, // 19: 두꺼운 U자 (안쪽 공간 넓음) new[]{ "000000000", "0#######0", "0#.....#0", "0#.....#0", "0#.....#0", "0#..#..#0", "0######0", "000000000" }, // 20: 작은 십자 변형 (팔 길이 짧음) new[]{ "000000000", "00###0000", "00#.#0000", "0###.###0", "0#.....#0", "0###.###0", "000#.#000", "000###000", "000000000" } }; // 중형(약 11~13) 마스크: Microban보다 넓은 홀과 복도 길이를 제공. public static readonly List Medium = new() { new[]{ "00000000000", "00#######00", "00#.....#00", "0##.###.##0", "0#..#.#..#0", "0#.......#0", "0#..#.#..#0", "0##.###.##0", "00#.....#00", "00#######00", "00000000000" }, new[]{ "0000000000000", "000#######000", "00##.....##00", "00#..###..#00", "0##.......##0", "0#..#...#..#0", "0##.......##0", "00#..###..#00", "00##.....##00", "000#######000", "0000000000000" }, new[]{ "000000000000", "00########00", "00#......#00", "0##.####.##0", "0#..#..#..#0", "0#..#..#..#0", "0##.####.##0", "00#......#00", "00###..###00", "000#....#000", "000######000", "000000000000" }, new[]{ "000000000000", "000######000", "000#....#000", "0###.##.###0", "0#......#.#0", "0#.##.##..#0", "0#..#.....#0", "0###.##.###0", "000#....#000", "000######000", "000000000000" }, new[]{ "0000000000000", "000#######000", "000#.....#000", "0###.###.###0", "0#...#.#...#0", "0#...#.#...#0", "0###.###.###0", "000#.....#000", "000#######000", "0000000000000" } }; // 대형(약 15~16) 마스크: 입구/포켓이 늘어나고, 복도 길이가 길다. public static readonly List Large = new() { new[]{ "000000000000000", "000#########000", "000#.......#000", "00##.#####.##00", "00#..#...#..#00", "0##..#...#..##0", "0#...#...#...#0", "0#...#####...#0", "0#...........#0", "0##..#...#..##0", "00#..#...#..#00", "00##.#####.##00", "000#.......#000", "000#########000", "000000000000000" }, new[]{ "000000000000000", "000#########000", "000#.......#000", "00##.#####.##00", "00#..#...#..#00", "0##..#.#.#..##0", "0#...##.##...#0", "0#...#...#...#0", "0#...##.##...#0", "0##..#.#.#..##0", "00#..#...#..#00", "00##.#####.##00", "000#.......#000", "000#########000", "000000000000000" }, new[]{ "0000000000000000", "0000#########000", "0000#.......#000", "00###.#####.###0", "00#..#.....#..#0", "0##..##...##..##", "0#....#...#....#", "0#.#..#####..#.#", "0#....#...#....#", "0##..##...##..##", "00#..#.....#..#0", "00###.#####.###0", "0000#.......#000", "0000#########000", "0000000000000000" }, new[]{ "000000000000000", "000##########00", "000#........#00", "00##.######.#00", "00#..#....#.#00", "0##..#....#..##", "0#...#.##.#...#", "0#...#....#...#", "0##..#....#..##", "00#..#....#.#00", "00##.######.#00", "000#........#00", "000##########00", "000000000000000" } }; public static string[] PickRandom(Random rng, List masks) { if (masks.Count == 0) throw new System.InvalidOperationException("No masks provided."); return masks[rng.Next(masks.Count)]; } public static List ExpandWithTransforms(IEnumerable baseMasks, int padMin = -1, int padMax = 1) { var seen = new HashSet(); var output = new List(); foreach (var mask in baseMasks) { var seeds = ScaleVariants(mask, padMin, padMax); foreach (var seed in seeds) { foreach (var variant in Variants(seed)) { var key = CanonicalKey(variant); if (seen.Add(key)) { output.Add(variant); } } } } return output; } public static string[] CreateVariant(string[] mask, Random rng, bool applyTransforms, int wallJitter) { var candidates = applyTransforms ? Variants(mask).ToList() : new List { mask }; var picked = candidates[rng.Next(candidates.Count)]; if (wallJitter <= 0) { return picked; } var jittered = ApplyWallJitter(picked, rng, wallJitter); return jittered; } private static IEnumerable Variants(string[] mask) { var current = mask; for (var i = 0; i < 4; i++) { yield return current; yield return FlipHorizontal(current); current = Rotate90(current); } } private static string[] Rotate90(string[] mask) { var h = mask.Length; var w = mask.Max(r => r.Length); var arr = new char[h, w]; for (var y = 0; y < h; y++) { var row = mask[y]; for (var x = 0; x < w; x++) { arr[y, x] = x < row.Length ? row[x] : '0'; } } var rotated = new string[w]; for (var y = 0; y < w; y++) { var chars = new char[h]; for (var x = 0; x < h; x++) { chars[x] = arr[h - 1 - x, y]; } rotated[y] = new string(chars); } return rotated; } private static string[] FlipHorizontal(string[] mask) { return mask.Select(row => new string(row.Reverse().ToArray())).ToArray(); } private static string CanonicalKey(string[] mask) => string.Join("\n", mask); private static string[] ApplyWallJitter(string[] mask, Random rng, int maxJitter) { var h = mask.Length; var w = mask.Max(r => r.Length); var grid = mask.Select(line => line.PadRight(w, '0').ToCharArray()).ToArray(); int changes = rng.Next(1, maxJitter + 1); var candidates = new List<(int x, int y)>(); for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var c = grid[y][x]; if (c == '0') continue; // avoid outermost void border flipping into wall if (y == 0 || x == 0 || y == h - 1 || x == w - 1) continue; candidates.Add((x, y)); } } Shuffle(candidates, rng); var applied = 0; foreach (var (x, y) in candidates) { if (applied >= changes) break; var c = grid[y][x]; if (c == '#') grid[y][x] = '.'; else if (c == '.') grid[y][x] = '#'; else continue; applied++; } var result = new string[h]; for (var y = 0; y < h; y++) { result[y] = new string(grid[y]); } return result; } private static void Shuffle(IList list, Random rng) { for (var i = list.Count - 1; i > 0; i--) { var j = rng.Next(i + 1); (list[i], list[j]) = (list[j], list[i]); } } private static List ScaleVariants(string[] mask, int padMin, int padMax) { var scaled = new List(); var from = Math.Min(padMin, padMax); var to = Math.Max(padMin, padMax); var addedBase = false; for (var delta = from; delta <= to; delta++) { if (delta == 0) { scaled.Add(mask); addedBase = true; continue; } var padded = Pad(mask, delta); if (padded != null) { scaled.Add(padded); } } if (!addedBase) { scaled.Insert(0, mask); } return scaled; } private static string[]? Pad(string[] mask, int delta) { if (delta == 0) return mask; var h = mask.Length; var w = mask.Max(r => r.Length); if (delta < 0) { if (h + delta * 2 < 3 || w + delta * 2 < 3) return null; var newH = h + delta * 2; var newW = w + delta * 2; var output = new string[newH]; for (var y = 0; y < newH; y++) { var srcY = y - delta; if (srcY < 0 || srcY >= h) { output[y] = new string('0', newW); continue; } var row = mask[srcY]; var src = row.Skip(delta).Take(newW).ToArray(); if (src.Length < newW) { output[y] = new string(src).PadRight(newW, '0'); } else { output[y] = new string(src); } } return output; } else { var newH = h + delta * 2; var newW = w + delta * 2; var output = new string[newH]; for (var y = 0; y < newH; y++) { if (y < delta || y >= h + delta) { output[y] = new string('0', newW); continue; } var row = mask[y - delta]; var paddedRow = new string('0', delta) + row.PadRight(w, '0') + new string('0', delta); if (paddedRow.Length < newW) { paddedRow = paddedRow.PadRight(newW, '0'); } output[y] = paddedRow; } return output; } } }