레벨 밸런스 1000까지 확장 및 박스 상한 규칙 적용

This commit is contained in:
JiWoong Sul
2025-12-04 14:48:11 +09:00
parent ed84c9d9e8
commit d073bc8814
11 changed files with 3323 additions and 607 deletions

View File

@@ -10,6 +10,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text; using System.Text;
internal static class Program internal static class Program
@@ -332,7 +333,8 @@ internal static class Program
Id = level.Id, Id = level.Id,
Grid = newGrid, Grid = newGrid,
LowestPush = level.LowestPush, LowestPush = level.LowestPush,
PushLimit = level.PushLimit PushLimit = level.PushLimit,
MoveCount = level.MoveCount
}); });
} }
@@ -395,7 +397,8 @@ internal static class Program
PocketMaxRadius = input.PocketMaxRadius, PocketMaxRadius = input.PocketMaxRadius,
MaskPadMin = padMin, MaskPadMin = padMin,
MaskPadMax = padMax, MaskPadMax = padMax,
ShapeMasks = masks ShapeMasks = masks,
ApplyTransforms = input.ApplyTransforms
}; };
} }
@@ -423,6 +426,21 @@ internal static class Program
case "large": case "large":
AddMasks(resolved, seen, MaskLibrary.Large); AddMasks(resolved, seen, MaskLibrary.Large);
break; break;
case "tall8":
AddMasks(resolved, seen, MaskLibrary.Tall8);
break;
case "tall9":
AddMasks(resolved, seen, MaskLibrary.Tall9);
break;
case "tall10":
AddMasks(resolved, seen, MaskLibrary.Tall10);
break;
case "tall11":
AddMasks(resolved, seen, MaskLibrary.Tall11);
break;
case "tall12":
AddMasks(resolved, seen, MaskLibrary.Tall12);
break;
} }
} }
} }
@@ -477,7 +495,7 @@ internal sealed class LevelGenerator
if (band.ShapeMasksExpanded == null || band.ShapeMasksExpanded.Count == 0) if (band.ShapeMasksExpanded == null || band.ShapeMasksExpanded.Count == 0)
{ {
var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban; var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban;
band.ShapeMasksExpanded = MaskLibrary.ExpandWithTransforms(baseMasks, band.MaskPadMin, band.MaskPadMax); band.ShapeMasksExpanded = MaskLibrary.ExpandWithTransforms(baseMasks, band.MaskPadMin, band.MaskPadMax, band.ApplyTransforms);
} }
} }
@@ -594,7 +612,7 @@ internal sealed class LevelGenerator
{ {
var failReasons = _trace ? new Dictionary<string, int>() : null; var failReasons = _trace ? new Dictionary<string, int>() : null;
var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban; var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban;
band.ShapeMasksExpanded ??= MaskLibrary.ExpandWithTransforms(baseMasks, band.MaskPadMin, band.MaskPadMax); band.ShapeMasksExpanded ??= MaskLibrary.ExpandWithTransforms(baseMasks, band.MaskPadMin, band.MaskPadMax, band.ApplyTransforms);
var pockets = ResolvePockets(band); var pockets = ResolvePockets(band);
var relaxSteps = overrideRelaxSteps ?? _tuning.RelaxationSteps; var relaxSteps = overrideRelaxSteps ?? _tuning.RelaxationSteps;
@@ -627,7 +645,7 @@ internal sealed class LevelGenerator
var mask = MaskLibrary.CreateVariant( var mask = MaskLibrary.CreateVariant(
MaskLibrary.PickRandom(rng, band.ShapeMasksExpanded), MaskLibrary.PickRandom(rng, band.ShapeMasksExpanded),
rng, rng,
_tuning.ApplyMaskTransforms, _tuning.ApplyMaskTransforms && band.ApplyTransforms,
_tuning.MaskWallJitter); _tuning.MaskWallJitter);
var canvas = LayoutFactory.FromMask(mask, rng, _tuning, pockets); var canvas = LayoutFactory.FromMask(mask, rng, _tuning, pockets);
@@ -723,7 +741,8 @@ internal sealed class LevelGenerator
Id = id, Id = id,
Grid = lines, Grid = lines,
LowestPush = solve.Pushes, LowestPush = solve.Pushes,
PushLimit = pushLimit PushLimit = pushLimit,
MoveCount = solve.Moves
}; };
return true; return true;
} }
@@ -765,6 +784,7 @@ internal sealed class LevelGenerator
PocketMaxRadius = src.PocketMaxRadius, PocketMaxRadius = src.PocketMaxRadius,
MaskPadMin = src.MaskPadMin, MaskPadMin = src.MaskPadMin,
MaskPadMax = src.MaskPadMax, MaskPadMax = src.MaskPadMax,
ApplyTransforms = src.ApplyTransforms,
ShapeMasks = src.ShapeMasks.ToList(), ShapeMasks = src.ShapeMasks.ToList(),
ShapeMasksExpanded = src.ShapeMasksExpanded?.ToList() ShapeMasksExpanded = src.ShapeMasksExpanded?.ToList()
}; };
@@ -824,7 +844,8 @@ internal sealed class LevelGenerator
Id = level.Id, Id = level.Id,
Grid = trimmed, Grid = trimmed,
LowestPush = level.LowestPush, LowestPush = level.LowestPush,
PushLimit = level.PushLimit PushLimit = level.PushLimit,
MoveCount = level.MoveCount
}; };
} }
} }
@@ -953,10 +974,20 @@ internal static class LevelVerifier
internal sealed class GeneratedLevel internal sealed class GeneratedLevel
{ {
[JsonPropertyOrder(0)]
public int Id { get; init; } public int Id { get; init; }
[JsonPropertyOrder(1)]
public List<string> Grid { get; init; } = new(); public List<string> Grid { get; init; } = new();
[JsonPropertyOrder(2)]
public int LowestPush { get; init; } public int LowestPush { get; init; }
[JsonPropertyOrder(3)]
public int PushLimit { get; init; } public int PushLimit { get; init; }
[JsonPropertyOrder(4)]
public int MoveCount { get; init; }
} }
internal sealed class LevelBandConfig internal sealed class LevelBandConfig
@@ -978,6 +1009,7 @@ internal sealed class LevelBandConfig
public int PocketMaxRadius { get; set; } = -1; public int PocketMaxRadius { get; set; } = -1;
public int MaskPadMin { get; set; } = -1; public int MaskPadMin { get; set; } = -1;
public int MaskPadMax { get; set; } = 1; public int MaskPadMax { get; set; } = 1;
public bool ApplyTransforms { get; set; } = true;
public List<string[]> ShapeMasks { get; set; } = new(); public List<string[]> ShapeMasks { get; set; } = new();
public List<string[]>? ShapeMasksExpanded { get; set; } public List<string[]>? ShapeMasksExpanded { get; set; }
} }
@@ -1006,6 +1038,7 @@ internal sealed class LevelBandJson
public int PocketMaxRadius { get; set; } = -1; public int PocketMaxRadius { get; set; } = -1;
public int MaskPadMin { get; set; } = -1; public int MaskPadMin { get; set; } = -1;
public int MaskPadMax { get; set; } = 1; public int MaskPadMax { get; set; } = 1;
public bool ApplyTransforms { get; set; } = true;
public List<string> MaskSets { get; set; } = new(); public List<string> MaskSets { get; set; } = new();
public int MaskTake { get; set; } = 0; public int MaskTake { get; set; } = 0;
} }

View File

@@ -48,13 +48,15 @@ dotnet run -- --trim <입력 json> [출력 json] [startId] [endId]
"000000000" "000000000"
], ],
"lowestPush": 5, "lowestPush": 5,
"pushLimit": 7 "pushLimit": 7,
"moveCount": 9
} }
] ]
``` ```
- `grid`: 문자열 배열(행). `0`은 외부 void, `#`는 벽, `.`은 바닥, `G/$/@`는 목표/박스/플레이어. - `grid`: 문자열 배열(행). `0`은 외부 void, `#`는 벽, `.`은 바닥, `G/$/@`는 목표/박스/플레이어.
- `lowestPush`: 솔버가 계산한 최소 푸시 수. - `lowestPush`: 솔버가 계산한 최소 푸시 수.
- `pushLimit`: 최소 푸시에 여유 패딩을 더한 제한 값. - `pushLimit`: 최소 푸시에 여유 패딩을 더한 제한 값.
- `moveCount`: 솔버가 계산한 최소 이동 수(걷기 + 푸시 포함).
## 메모 ## 메모
- 외벽은 항상 `#`로 둘러지며, `0`은 외부에서만 사용됩니다. - 외벽은 항상 `#`로 둘러지며, `0`은 외부에서만 사용됩니다.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -352,13 +352,221 @@ internal static class MaskLibrary
} }
}; };
// 세로 확장형 마스크(폭 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) public static string[] PickRandom(Random rng, List<string[]> masks)
{ {
if (masks.Count == 0) throw new System.InvalidOperationException("No masks provided."); if (masks.Count == 0) throw new System.InvalidOperationException("No masks provided.");
return masks[rng.Next(masks.Count)]; return masks[rng.Next(masks.Count)];
} }
public static List<string[]> ExpandWithTransforms(IEnumerable<string[]> baseMasks, int padMin = -1, int padMax = 1) public static List<string[]> ExpandWithTransforms(IEnumerable<string[]> baseMasks, int padMin = -1, int padMax = 1, bool allowTransforms = true)
{ {
var seen = new HashSet<string>(); var seen = new HashSet<string>();
var output = new List<string[]>(); var output = new List<string[]>();
@@ -367,7 +575,8 @@ internal static class MaskLibrary
var seeds = ScaleVariants(mask, padMin, padMax); var seeds = ScaleVariants(mask, padMin, padMax);
foreach (var seed in seeds) foreach (var seed in seeds)
{ {
foreach (var variant in Variants(seed)) var variants = allowTransforms ? Variants(seed) : new[] { seed };
foreach (var variant in variants)
{ {
var key = CanonicalKey(variant); var key = CanonicalKey(variant);
if (seen.Add(key)) if (seen.Add(key))

View File

@@ -1 +1 @@
7886d3225e6b0aa6234a1c3da555a0a6b7dccd0b c99ea306e8f2077bc6d6b8c28e9c7dd567fe8bf1

Binary file not shown.

Binary file not shown.