Files
nekoban_map_generator/mask_library.cs

787 lines
20 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"
}
};
// 세로 확장형 마스크(폭 8~12, 회전 없이 사용)
public static readonly List<string[]> Tall8 = new()
{
new[]{
"########",
"#......#",
"#.####.#",
"#.#..#.#",
"#.#..#.#",
"#.####.#",
"#......#",
"########"
},
new[]{
"########",
"#..##..#",
"#..##..#",
"#......#",
"#.####.#",
"#.#..#.#",
"#......#",
"###..###",
"########"
},
new[]{
"########",
"#......#",
"##.##.##",
"#......#",
"#.####.#",
"#......#",
"##.##.##",
"#......#",
"########"
}
};
public static readonly List<string[]> Tall9 = new()
{
new[]{
"#########",
"#.......#",
"#.#####.#",
"#.#...#.#",
"#.#.#.#.#",
"#.#...#.#",
"#.#####.#",
"#.......#",
"#########"
},
new[]{
"#########",
"#...#...#",
"#...#...#",
"###.#.###",
"#.......#",
"#.#####.#",
"#.#...#.#",
"#.#...#.#",
"#...#...#",
"#########"
},
new[]{
"#########",
"#.......#",
"#.###.#.#",
"#.#.#.#.#",
"#.#.#.#.#",
"#.#.#.#.#",
"#.###.#.#",
"#.......#",
"#.###.#.#",
"#.......#",
"#########"
}
};
public static readonly List<string[]> Tall10 = new()
{
new[]{
"##########",
"#........#",
"##.####.##",
"#..#..#..#",
"#..####..#",
"#........#",
"###.##.###",
"#........#",
"##.####.##",
"##########"
},
new[]{
"##########",
"#...##...#",
"#...##...#",
"###.##.###",
"#........#",
"#.######.#",
"#.#....#.#",
"#.######.#",
"#........#",
"###.##.###",
"#...##...#",
"##########"
},
new[]{
"##########",
"#........#",
"#.######.#",
"#.#....#.#",
"#.#.##.#.#",
"#.#.##.#.#",
"#.#....#.#",
"#.######.#",
"#........#",
"###.##.###",
"#........#",
"#.######.#",
"##########"
}
};
public static readonly List<string[]> Tall11 = new()
{
new[]{
"###########",
"#.........#",
"#.#######.#",
"#.#.....#.#",
"#.#.###.#.#",
"#.#.#.#.#.#",
"#.#.###.#.#",
"#.#.....#.#",
"#.#######.#",
"#.........#",
"###.###.###",
"#.........#",
"#.#######.#",
"#.#.....#.#",
"#.#.###.#.#",
"###########"
},
new[]{
"###########",
"#....#....#",
"#....#....#",
"###.#.#.###",
"#.........#",
"#.#######.#",
"#.#.....#.#",
"#.#.###.#.#",
"#.#.#.#.#.#",
"#.#.###.#.#",
"#.#.....#.#",
"#.#######.#",
"#.........#",
"###.#.#.###",
"#....#....#",
"#....#....#",
"###.....###",
"###########"
}
};
public static readonly List<string[]> Tall12 = new()
{
new[]{
"############",
"#..........#",
"#.########.#",
"#.#......#.#",
"#.#.####.#.#",
"#.#.#..#.#.#",
"#.#.#..#.#.#",
"#.#.####.#.#",
"#.#......#.#",
"#.########.#",
"#..........#",
"###.####.###",
"#..........#",
"#.########.#",
"#.#......#.#",
"#.#.####.#.#",
"#..........#",
"############"
},
new[]{
"############",
"#....##....#",
"#....##....#",
"###.####.###",
"#..........#",
"#.########.#",
"#.#......#.#",
"#.#.####.#.#",
"#.#.#..#.#.#",
"#.#.####.#.#",
"#.#......#.#",
"#.########.#",
"#..........#",
"###.####.###",
"#....##....#",
"#....##....#",
"###......###",
"############"
}
};
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, bool allowTransforms = true)
{
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)
{
var variants = allowTransforms ? Variants(seed) : new[] { seed };
foreach (var variant in variants)
{
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;
}
}
}