578 lines
15 KiB
C#
578 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
internal static class MaskLibrary
|
|
{
|
|
public static readonly List<string[]> 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<string[]> 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<string[]> 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<string[]> masks)
|
|
{
|
|
if (masks.Count == 0) throw new System.InvalidOperationException("No masks provided.");
|
|
return masks[rng.Next(masks.Count)];
|
|
}
|
|
|
|
public static List<string[]> ExpandWithTransforms(IEnumerable<string[]> baseMasks, int padMin = -1, int padMax = 1)
|
|
{
|
|
var seen = new HashSet<string>();
|
|
var output = new List<string[]>();
|
|
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<string[]> { mask };
|
|
var picked = candidates[rng.Next(candidates.Count)];
|
|
|
|
if (wallJitter <= 0)
|
|
{
|
|
return picked;
|
|
}
|
|
|
|
var jittered = ApplyWallJitter(picked, rng, wallJitter);
|
|
return jittered;
|
|
}
|
|
|
|
private static IEnumerable<string[]> 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<T>(IList<T> 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<string[]> ScaleVariants(string[] mask, int padMin, int padMax)
|
|
{
|
|
var scaled = new List<string[]>();
|
|
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;
|
|
}
|
|
}
|
|
}
|