신규등록
This commit is contained in:
989
Program copy.cs.bak
Normal file
989
Program copy.cs.bak
Normal file
@@ -0,0 +1,989 @@
|
|||||||
|
// Sokoban map generator (mask-based, reverse search from solved state)
|
||||||
|
// - Uses small hand-made masks (Microban/Novoban 스타일) to shape the outer walls.
|
||||||
|
// - Places goals/boxes in solved state, then pulls boxes away from goals (reverse of push) to guarantee solvability.
|
||||||
|
// - Run: `dotnet run > output.json` (defaults use band config). Optional: `dotnet run <seed> [startId] [endId]`.
|
||||||
|
// Legend: '#' wall, '.' floor, '0' void, 'G' goal, '$' box, '@' player.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static readonly LevelBandConfig[] LevelBands =
|
||||||
|
{
|
||||||
|
new LevelBandConfig
|
||||||
|
{
|
||||||
|
StartId = 3,
|
||||||
|
EndId = 20,
|
||||||
|
BoxCountLow = 1,
|
||||||
|
BoxCountHigh = 2,
|
||||||
|
MinAllowedPushes = 4,
|
||||||
|
MinAllowedTurns = 2,
|
||||||
|
ShapeMasks = MaskLibrary.Microban.Take(8).ToList()
|
||||||
|
},
|
||||||
|
new LevelBandConfig
|
||||||
|
{
|
||||||
|
StartId = 21,
|
||||||
|
EndId = 40,
|
||||||
|
BoxCountLow = 1,
|
||||||
|
BoxCountHigh = 2,
|
||||||
|
MinAllowedPushes = 7,
|
||||||
|
MinAllowedTurns = 3,
|
||||||
|
ShapeMasks = MaskLibrary.Microban.ToList()
|
||||||
|
},
|
||||||
|
new LevelBandConfig
|
||||||
|
{
|
||||||
|
StartId = 41,
|
||||||
|
EndId = 60,
|
||||||
|
BoxCountLow = 2,
|
||||||
|
BoxCountHigh = 3,
|
||||||
|
MinAllowedPushes = 9,
|
||||||
|
MinAllowedTurns = 4,
|
||||||
|
ShapeMasks = MaskLibrary.Microban.ToList()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GenerationTuning Tuning = new GenerationTuning
|
||||||
|
{
|
||||||
|
MaxAttemptsPerLevel = 2000,
|
||||||
|
MaxMillisecondsPerLevel = 40_000,
|
||||||
|
PushLimitPadding = 2,
|
||||||
|
PushLimitScale = 0.35,
|
||||||
|
DynamicGrowthWindow = 12,
|
||||||
|
DynamicPushIncrement = 2,
|
||||||
|
ReverseSearchMaxDepth = 120,
|
||||||
|
ReverseSearchBreadth = 800,
|
||||||
|
ApplyMaskTransforms = true,
|
||||||
|
MaskWallJitter = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
private const int DefaultSeed = 12345;
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var seed = DefaultSeed;
|
||||||
|
if (args.Length > 0 && int.TryParse(args[0], out var parsedSeed))
|
||||||
|
{
|
||||||
|
seed = parsedSeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rng = new Random(seed);
|
||||||
|
var generator = new LevelGenerator(rng, Tuning, LevelBands);
|
||||||
|
|
||||||
|
var startId = LevelBands.Min(b => b.StartId);
|
||||||
|
var endId = LevelBands.Max(b => b.EndId);
|
||||||
|
|
||||||
|
if (args.Length >= 2 && int.TryParse(args[1], out var requestedStart))
|
||||||
|
{
|
||||||
|
startId = requestedStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length >= 3 && int.TryParse(args[2], out var requestedEnd))
|
||||||
|
{
|
||||||
|
endId = requestedEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startId > endId)
|
||||||
|
{
|
||||||
|
(startId, endId) = (endId, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levels = generator.BuildRange(startId, endId);
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(levels, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class LevelGenerator
|
||||||
|
{
|
||||||
|
private readonly Random _rng;
|
||||||
|
private readonly GenerationTuning _tuning;
|
||||||
|
private readonly IReadOnlyList<LevelBandConfig> _bands;
|
||||||
|
private readonly bool _trace;
|
||||||
|
private readonly HashSet<string> _seenLayouts = new();
|
||||||
|
|
||||||
|
public LevelGenerator(Random rng, GenerationTuning tuning, IReadOnlyList<LevelBandConfig> bands)
|
||||||
|
{
|
||||||
|
_rng = rng;
|
||||||
|
_tuning = tuning;
|
||||||
|
_bands = bands;
|
||||||
|
_trace = Environment.GetEnvironmentVariable("NEKOBAN_DEBUG") == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GeneratedLevel> BuildRange(int startId, int endId)
|
||||||
|
{
|
||||||
|
foreach (var band in _bands)
|
||||||
|
{
|
||||||
|
if (band.ShapeMasksExpanded == null || band.ShapeMasksExpanded.Count == 0)
|
||||||
|
{
|
||||||
|
var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban;
|
||||||
|
band.ShapeMasksExpanded = MaskLibrary.ExpandWithTransforms(baseMasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = new List<GeneratedLevel>();
|
||||||
|
for (var id = startId; id <= endId; id++)
|
||||||
|
{
|
||||||
|
var band = ResolveBand(id);
|
||||||
|
var level = BuildSingle(id, band);
|
||||||
|
output.Add(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GeneratedLevel BuildSingle(int id, LevelBandConfig band)
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
var attempts = 0;
|
||||||
|
var failReasons = _trace ? new Dictionary<string, int>() : null;
|
||||||
|
|
||||||
|
while (attempts < _tuning.MaxAttemptsPerLevel &&
|
||||||
|
stopwatch.ElapsedMilliseconds < _tuning.MaxMillisecondsPerLevel)
|
||||||
|
{
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
var baseMasks = band.ShapeMasks.Count > 0 ? band.ShapeMasks : MaskLibrary.Microban;
|
||||||
|
var prepped = band.ShapeMasksExpanded ?? MaskLibrary.ExpandWithTransforms(baseMasks);
|
||||||
|
band.ShapeMasksExpanded ??= prepped;
|
||||||
|
var mask = MaskLibrary.CreateVariant(
|
||||||
|
MaskLibrary.PickRandom(_rng, prepped),
|
||||||
|
_rng,
|
||||||
|
_tuning.ApplyMaskTransforms,
|
||||||
|
_tuning.MaskWallJitter);
|
||||||
|
|
||||||
|
var canvas = LayoutFactory.FromMask(mask, _rng, _tuning);
|
||||||
|
|
||||||
|
var boxCount = _rng.Next(band.BoxCountLow, band.BoxCountHigh + 1);
|
||||||
|
if (boxCount <= 0)
|
||||||
|
{
|
||||||
|
boxCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PiecePlacer.TryPlace(canvas, _rng, boxCount, out var goals, out var boxes, out var player))
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "place");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var board = Board.FromCanvas(canvas);
|
||||||
|
var reverse = new ReverseSearch(board, _tuning.ReverseSearchMaxDepth, _tuning.ReverseSearchBreadth);
|
||||||
|
var startState = reverse.FindStartState(goals, band.MinAllowedPushes, _rng);
|
||||||
|
if (startState == null)
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "reverse_not_found");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var solver = new SokobanSolver(board);
|
||||||
|
var solve = solver.SolveDetailed(startState.Player, startState.Boxes, goals);
|
||||||
|
if (solve.IsFail)
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "verify_unsolvable");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solve.Pushes < band.MinAllowedPushes || solve.Turns < band.MinAllowedTurns || solve.Branching < band.MinAllowedBranching)
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "too_easy");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pushLimit = solve.Pushes + Math.Max(_tuning.PushLimitPadding,
|
||||||
|
(int)Math.Ceiling(solve.Pushes * _tuning.PushLimitScale));
|
||||||
|
|
||||||
|
canvas.ClearDynamic();
|
||||||
|
canvas.Overlay(goals, 'G');
|
||||||
|
canvas.Overlay(startState.Boxes, '$');
|
||||||
|
canvas.Set(startState.Player, '@');
|
||||||
|
|
||||||
|
// Final wall normalization; reject if tokens end up on the edge.
|
||||||
|
if (!LayoutFactory.NormalizeOuterWallsStrict(canvas, failOnTokens: true))
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "boundary");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = canvas.ToLines();
|
||||||
|
var layoutKey = string.Join("|", lines);
|
||||||
|
if (_seenLayouts.Contains(layoutKey))
|
||||||
|
{
|
||||||
|
NoteFail(failReasons, "duplicate");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_seenLayouts.Add(layoutKey);
|
||||||
|
|
||||||
|
return new GeneratedLevel
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Grid = lines,
|
||||||
|
LowestPush = solve.Pushes,
|
||||||
|
PushLimit = pushLimit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_trace && failReasons != null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[trace] level {id} failed. Reasons:");
|
||||||
|
foreach (var kv in failReasons.OrderByDescending(kv => kv.Value))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($" - {kv.Key}: {kv.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"레벨 {id} 생성 실패 (attempts {attempts}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
private LevelBandConfig ResolveBand(int id)
|
||||||
|
{
|
||||||
|
foreach (var band in _bands)
|
||||||
|
{
|
||||||
|
if (id >= band.StartId && id <= band.EndId)
|
||||||
|
{
|
||||||
|
return band;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var last = _bands.Last();
|
||||||
|
var delta = id - last.EndId;
|
||||||
|
var growthStep = Math.Max(1, delta / Math.Max(1, _tuning.DynamicGrowthWindow) + 1);
|
||||||
|
return new LevelBandConfig
|
||||||
|
{
|
||||||
|
StartId = id,
|
||||||
|
EndId = id,
|
||||||
|
BoxCountLow = last.BoxCountLow + growthStep / 2,
|
||||||
|
BoxCountHigh = last.BoxCountHigh + growthStep / 2,
|
||||||
|
MinAllowedPushes = last.MinAllowedPushes + growthStep * _tuning.DynamicPushIncrement,
|
||||||
|
ShapeMasks = last.ShapeMasks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void NoteFail(IDictionary<string, int>? sink, string reason)
|
||||||
|
{
|
||||||
|
if (sink == null) return;
|
||||||
|
sink[reason] = sink.TryGetValue(reason, out var count) ? count + 1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GeneratedLevel
|
||||||
|
{
|
||||||
|
public int Id { get; init; }
|
||||||
|
public List<string> Grid { get; init; } = new();
|
||||||
|
public int LowestPush { get; init; }
|
||||||
|
public int PushLimit { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class LevelBandConfig
|
||||||
|
{
|
||||||
|
public int StartId { get; init; }
|
||||||
|
public int EndId { get; init; }
|
||||||
|
public int BoxCountLow { get; init; }
|
||||||
|
public int BoxCountHigh { get; init; }
|
||||||
|
public int MinAllowedPushes { get; init; }
|
||||||
|
public int MinAllowedTurns { get; init; } = 0;
|
||||||
|
public int MinAllowedBranching { get; init; } = 0;
|
||||||
|
public List<string[]> ShapeMasks { get; init; } = new();
|
||||||
|
public List<string[]>? ShapeMasksExpanded { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GenerationTuning
|
||||||
|
{
|
||||||
|
public int MaxAttemptsPerLevel { get; init; }
|
||||||
|
public int MaxMillisecondsPerLevel { get; init; }
|
||||||
|
public int PushLimitPadding { get; init; }
|
||||||
|
public double PushLimitScale { get; init; }
|
||||||
|
public int DynamicGrowthWindow { get; init; }
|
||||||
|
public int DynamicPushIncrement { get; init; }
|
||||||
|
public int ReverseSearchMaxDepth { get; init; }
|
||||||
|
public int ReverseSearchBreadth { get; init; }
|
||||||
|
public bool ApplyMaskTransforms { get; init; }
|
||||||
|
public int MaskWallJitter { get; init; }
|
||||||
|
public int PocketCarveMin { get; init; } = 0;
|
||||||
|
public int PocketCarveMax { get; init; } = 0;
|
||||||
|
public int PocketMaxRadius { get; init; } = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GridCanvas
|
||||||
|
{
|
||||||
|
private readonly char[] _cells;
|
||||||
|
|
||||||
|
public GridCanvas(int width, int height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
_cells = Enumerable.Repeat('0', width * height).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
public void Fill(char c) => Array.Fill(_cells, c);
|
||||||
|
|
||||||
|
public void Set(int x, int y, char c)
|
||||||
|
{
|
||||||
|
if (!InBounds(x, y)) return;
|
||||||
|
_cells[y * Width + x] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(int index, char c)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= _cells.Length) return;
|
||||||
|
_cells[index] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char Get(int x, int y) => _cells[y * Width + x];
|
||||||
|
public char Get(int index) => _cells[index];
|
||||||
|
|
||||||
|
public bool InBounds(int x, int y) => x >= 0 && y >= 0 && x < Width && y < Height;
|
||||||
|
|
||||||
|
public void Overlay(IEnumerable<int> indices, char c)
|
||||||
|
{
|
||||||
|
foreach (var idx in indices)
|
||||||
|
{
|
||||||
|
Set(idx, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearDynamic()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _cells.Length; i++)
|
||||||
|
{
|
||||||
|
if (_cells[i] == '@' || _cells[i] == '$')
|
||||||
|
{
|
||||||
|
_cells[i] = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> ToLines()
|
||||||
|
{
|
||||||
|
var lines = new List<string>(Height);
|
||||||
|
for (var y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
lines.Add(new string(_cells, y * Width, Width));
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class LayoutFactory
|
||||||
|
{
|
||||||
|
public static GridCanvas FromMask(string[] mask, Random rng, GenerationTuning tuning)
|
||||||
|
{
|
||||||
|
var height = mask.Length;
|
||||||
|
var width = mask.Max(row => row.Length);
|
||||||
|
var canvas = new GridCanvas(width, height);
|
||||||
|
canvas.Fill('0');
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
var row = mask[y];
|
||||||
|
for (var x = 0; x < row.Length; x++)
|
||||||
|
{
|
||||||
|
canvas.Set(x, y, row[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalizeOuterWalls(canvas);
|
||||||
|
|
||||||
|
if (tuning.PocketCarveMax > 0)
|
||||||
|
{
|
||||||
|
AddPockets(canvas, rng, tuning);
|
||||||
|
NormalizeOuterWalls(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce: inside the bounding box of non-void tiles, edges are walls, interior voids become walls.
|
||||||
|
private static void NormalizeOuterWalls(GridCanvas canvas)
|
||||||
|
{
|
||||||
|
var w = canvas.Width;
|
||||||
|
var h = canvas.Height;
|
||||||
|
|
||||||
|
var minX = w;
|
||||||
|
var minY = h;
|
||||||
|
var maxX = -1;
|
||||||
|
var maxY = -1;
|
||||||
|
|
||||||
|
for (var y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
var c = canvas.Get(x, y);
|
||||||
|
if (c == '0') continue;
|
||||||
|
if (x < minX) minX = x;
|
||||||
|
if (y < minY) minY = y;
|
||||||
|
if (x > maxX) maxX = x;
|
||||||
|
if (y > maxY) maxY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxX < minX || maxY < minY)
|
||||||
|
{
|
||||||
|
return; // nothing to normalize
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
for (var x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
var isEdge = x == minX || x == maxX || y == minY || y == maxY;
|
||||||
|
if (isEdge)
|
||||||
|
{
|
||||||
|
canvas.Set(x, y, '#');
|
||||||
|
}
|
||||||
|
else if (canvas.Get(x, y) == '0')
|
||||||
|
{
|
||||||
|
canvas.Set(x, y, '#');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outside bounding box remains void.
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool NormalizeOuterWallsStrict(GridCanvas canvas, bool failOnTokens)
|
||||||
|
{
|
||||||
|
var w = canvas.Width;
|
||||||
|
var h = canvas.Height;
|
||||||
|
|
||||||
|
var minX = w;
|
||||||
|
var minY = h;
|
||||||
|
var maxX = -1;
|
||||||
|
var maxY = -1;
|
||||||
|
|
||||||
|
for (var y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
var c = canvas.Get(x, y);
|
||||||
|
if (c == '0') continue;
|
||||||
|
if (x < minX) minX = x;
|
||||||
|
if (y < minY) minY = y;
|
||||||
|
if (x > maxX) maxX = x;
|
||||||
|
if (y > maxY) maxY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxX < minX || maxY < minY)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
for (var x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
var isEdge = x == minX || x == maxX || y == minY || y == maxY;
|
||||||
|
var c = canvas.Get(x, y);
|
||||||
|
if (isEdge)
|
||||||
|
{
|
||||||
|
if (failOnTokens && (c == 'G' || c == '$' || c == '@'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
canvas.Set(x, y, '#');
|
||||||
|
}
|
||||||
|
else if (c == '0')
|
||||||
|
{
|
||||||
|
canvas.Set(x, y, '#');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddPockets(GridCanvas canvas, Random rng, GenerationTuning tuning)
|
||||||
|
{
|
||||||
|
var pockets = rng.Next(tuning.PocketCarveMin, tuning.PocketCarveMax + 1);
|
||||||
|
for (var i = 0; i < pockets; i++)
|
||||||
|
{
|
||||||
|
var radius = rng.Next(1, tuning.PocketMaxRadius + 1);
|
||||||
|
var x = rng.Next(1, canvas.Width - 1);
|
||||||
|
var y = rng.Next(1, canvas.Height - 1);
|
||||||
|
for (var yy = y - radius; yy <= y + radius; yy++)
|
||||||
|
{
|
||||||
|
for (var xx = x - radius; xx <= x + radius; xx++)
|
||||||
|
{
|
||||||
|
if (!canvas.InBounds(xx, yy)) continue;
|
||||||
|
if (xx == 0 || yy == 0 || xx == canvas.Width - 1 || yy == canvas.Height - 1) continue;
|
||||||
|
var dx = xx - x;
|
||||||
|
var dy = yy - y;
|
||||||
|
if (dx * dx + dy * dy <= radius * radius)
|
||||||
|
{
|
||||||
|
if (canvas.Get(xx, yy) == '#')
|
||||||
|
{
|
||||||
|
canvas.Set(xx, yy, '.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class PiecePlacer
|
||||||
|
{
|
||||||
|
public static bool TryPlace(GridCanvas canvas, Random rng, int boxCount, out HashSet<int> goals, out int[] boxes, out int player)
|
||||||
|
{
|
||||||
|
goals = new HashSet<int>();
|
||||||
|
boxes = Array.Empty<int>();
|
||||||
|
player = -1;
|
||||||
|
|
||||||
|
var candidates = Walkable(canvas).ToList();
|
||||||
|
var minRequired = boxCount + 2;
|
||||||
|
if (candidates.Count < minRequired)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shuffle(candidates, rng);
|
||||||
|
|
||||||
|
var softGoals = new List<int>();
|
||||||
|
foreach (var idx in candidates)
|
||||||
|
{
|
||||||
|
if (IsDeadCorner(canvas, idx))
|
||||||
|
{
|
||||||
|
softGoals.Add(idx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
goals.Add(idx);
|
||||||
|
if (goals.Count == boxCount) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goals.Count != boxCount)
|
||||||
|
{
|
||||||
|
foreach (var idx in softGoals)
|
||||||
|
{
|
||||||
|
goals.Add(idx);
|
||||||
|
if (goals.Count == boxCount) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goals.Count != boxCount) return false;
|
||||||
|
|
||||||
|
var goalSet = new HashSet<int>(goals);
|
||||||
|
var nonGoals = candidates.Where(i => !goalSet.Contains(i)).ToList();
|
||||||
|
Shuffle(nonGoals, rng);
|
||||||
|
|
||||||
|
boxes = goals.ToArray();
|
||||||
|
Array.Sort(boxes);
|
||||||
|
|
||||||
|
if (nonGoals.Count == 0) return false;
|
||||||
|
|
||||||
|
var reachable = FloodFrom(goalSet.First(), canvas, Array.Empty<int>());
|
||||||
|
if (goalSet.Any(g => !reachable.Contains(g))) return false;
|
||||||
|
|
||||||
|
var playerOptions = nonGoals.Where(reachable.Contains).ToList();
|
||||||
|
if (playerOptions.Count == 0) return false;
|
||||||
|
|
||||||
|
player = playerOptions[rng.Next(playerOptions.Count)];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<int> FloodFrom(int start, GridCanvas canvas, int[] boxes)
|
||||||
|
{
|
||||||
|
var visited = new HashSet<int>();
|
||||||
|
if (IsSolid(canvas.Get(start)) || Array.BinarySearch(boxes, start) >= 0) return visited;
|
||||||
|
|
||||||
|
var queue = new Queue<int>();
|
||||||
|
visited.Add(start);
|
||||||
|
queue.Enqueue(start);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var current = queue.Dequeue();
|
||||||
|
foreach (var next in NeighborIndices(current, canvas.Width, canvas.Height))
|
||||||
|
{
|
||||||
|
if (next < 0 || next >= canvas.Width * canvas.Height) continue;
|
||||||
|
if (visited.Contains(next)) continue;
|
||||||
|
|
||||||
|
var tile = canvas.Get(next);
|
||||||
|
if (IsSolid(tile) || Array.BinarySearch(boxes, next) >= 0) continue;
|
||||||
|
|
||||||
|
visited.Add(next);
|
||||||
|
queue.Enqueue(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<int> Walkable(GridCanvas canvas)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < canvas.Width * canvas.Height; i++)
|
||||||
|
{
|
||||||
|
var tile = canvas.Get(i);
|
||||||
|
if (!IsSolid(tile))
|
||||||
|
{
|
||||||
|
yield return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<int> NeighborIndices(int index, int width, int height)
|
||||||
|
{
|
||||||
|
var x = index % width;
|
||||||
|
var y = index / width;
|
||||||
|
|
||||||
|
if (y > 0) yield return index - width;
|
||||||
|
if (y < height - 1) yield return index + width;
|
||||||
|
if (x > 0) yield return index - 1;
|
||||||
|
if (x < width - 1) yield return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDeadCorner(GridCanvas canvas, int idx)
|
||||||
|
{
|
||||||
|
var w = canvas.Width;
|
||||||
|
var h = canvas.Height;
|
||||||
|
var x = idx % w;
|
||||||
|
var y = idx / w;
|
||||||
|
|
||||||
|
var solidNorth = y == 0 || IsSolid(canvas.Get(x, y - 1));
|
||||||
|
var solidSouth = y == h - 1 || IsSolid(canvas.Get(x, y + 1));
|
||||||
|
var solidWest = x == 0 || IsSolid(canvas.Get(x - 1, y));
|
||||||
|
var solidEast = x == w - 1 || IsSolid(canvas.Get(x + 1, y));
|
||||||
|
|
||||||
|
var verticalBlocked = (solidNorth && solidSouth);
|
||||||
|
var horizontalBlocked = (solidWest && solidEast);
|
||||||
|
if (verticalBlocked || horizontalBlocked) return true;
|
||||||
|
|
||||||
|
var cornerNW = solidNorth && solidWest;
|
||||||
|
var cornerNE = solidNorth && solidEast;
|
||||||
|
var cornerSW = solidSouth && solidWest;
|
||||||
|
var cornerSE = solidSouth && solidEast;
|
||||||
|
return cornerNW || cornerNE || cornerSW || cornerSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSolid(char tile) => tile == '#' || tile == '0';
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Board
|
||||||
|
{
|
||||||
|
private readonly bool[] _walls;
|
||||||
|
|
||||||
|
private Board(int width, int height, bool[] walls)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
_walls = walls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
public int Size => Width * Height;
|
||||||
|
|
||||||
|
public static Board FromCanvas(GridCanvas canvas)
|
||||||
|
{
|
||||||
|
var walls = new bool[canvas.Width * canvas.Height];
|
||||||
|
for (var i = 0; i < walls.Length; i++)
|
||||||
|
{
|
||||||
|
var tile = canvas.Get(i);
|
||||||
|
walls[i] = tile == '#' || tile == '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Board(canvas.Width, canvas.Height, walls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSolid(int index) => _walls[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ReverseSearch
|
||||||
|
{
|
||||||
|
private readonly Board _board;
|
||||||
|
private readonly int _maxDepth;
|
||||||
|
private readonly int _breadth;
|
||||||
|
private readonly int[] _dirs;
|
||||||
|
|
||||||
|
public ReverseSearch(Board board, int maxDepth, int breadth)
|
||||||
|
{
|
||||||
|
_board = board;
|
||||||
|
_maxDepth = maxDepth;
|
||||||
|
_breadth = breadth;
|
||||||
|
_dirs = new[] { -board.Width, board.Width, -1, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScrambledState? FindStartState(HashSet<int> goals, int minPush, Random rng)
|
||||||
|
{
|
||||||
|
var boxes = goals.ToArray();
|
||||||
|
Array.Sort(boxes);
|
||||||
|
|
||||||
|
var playerStart = FirstFloorNotGoal(goals);
|
||||||
|
if (playerStart < 0) return null;
|
||||||
|
|
||||||
|
var start = new SolverState(playerStart, boxes);
|
||||||
|
var queue = new Queue<(SolverState State, int Pushes)>();
|
||||||
|
var visited = new HashSet<SolverState>(new StateComparer()) { start };
|
||||||
|
queue.Enqueue((start, 0));
|
||||||
|
|
||||||
|
ScrambledState? best = null;
|
||||||
|
var bestPush = -1;
|
||||||
|
|
||||||
|
while (queue.Count > 0 && visited.Count < _breadth)
|
||||||
|
{
|
||||||
|
var (state, pushes) = queue.Dequeue();
|
||||||
|
if (pushes >= minPush && pushes > bestPush)
|
||||||
|
{
|
||||||
|
bestPush = pushes;
|
||||||
|
best = new ScrambledState(state.Player, state.Boxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushes >= _maxDepth) continue;
|
||||||
|
|
||||||
|
foreach (var next in Expand(state))
|
||||||
|
{
|
||||||
|
var nextPushes = pushes + (next.Item2 ? 1 : 0);
|
||||||
|
if (nextPushes > _maxDepth) continue;
|
||||||
|
|
||||||
|
var nextState = next.Item1;
|
||||||
|
if (visited.Add(nextState))
|
||||||
|
{
|
||||||
|
queue.Enqueue((nextState, nextPushes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FirstFloorNotGoal(HashSet<int> goals)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _board.Size; i++)
|
||||||
|
{
|
||||||
|
if (_board.IsSolid(i)) continue;
|
||||||
|
if (goals.Contains(i)) continue;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns (state, isPull) where isPull indicates a box move (counts as push)
|
||||||
|
private IEnumerable<(SolverState, bool)> Expand(SolverState state)
|
||||||
|
{
|
||||||
|
foreach (var dir in _dirs)
|
||||||
|
{
|
||||||
|
var step = state.Player + dir;
|
||||||
|
if (!IndexInBounds(step) || _board.IsSolid(step) || Array.BinarySearch(state.Boxes, step) >= 0)
|
||||||
|
{
|
||||||
|
// pull candidate?
|
||||||
|
var boxPos = state.Player + dir;
|
||||||
|
var behind = state.Player - dir;
|
||||||
|
if (!IndexInBounds(boxPos) || !IndexInBounds(behind)) continue;
|
||||||
|
var boxIndex = Array.BinarySearch(state.Boxes, boxPos);
|
||||||
|
if (boxIndex < 0) continue;
|
||||||
|
if (_board.IsSolid(behind) || Array.BinarySearch(state.Boxes, behind) >= 0) continue;
|
||||||
|
|
||||||
|
var nextBoxes = state.Boxes.ToArray();
|
||||||
|
nextBoxes[boxIndex] = state.Player;
|
||||||
|
Array.Sort(nextBoxes);
|
||||||
|
yield return (new SolverState(behind, nextBoxes), true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// player walk
|
||||||
|
yield return (new SolverState(step, state.Boxes), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IndexInBounds(int idx) => idx >= 0 && idx < _board.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class SokobanSolver
|
||||||
|
{
|
||||||
|
private readonly Board _board;
|
||||||
|
private readonly StateComparer _comparer = new();
|
||||||
|
private readonly int[] _dirs;
|
||||||
|
|
||||||
|
public SokobanSolver(Board board)
|
||||||
|
{
|
||||||
|
_board = board;
|
||||||
|
_dirs = new[] { -board.Width, board.Width, -1, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolveResult SolveDetailed(int player, int[] boxes, HashSet<int> goals)
|
||||||
|
{
|
||||||
|
Array.Sort(boxes);
|
||||||
|
var start = new SolverState(player, boxes);
|
||||||
|
var visited = new HashSet<SolverState>(_comparer) { start };
|
||||||
|
var parents = new Dictionary<SolverState, (SolverState Parent, int Dir)>(_comparer);
|
||||||
|
var queue = new Queue<SolverState>();
|
||||||
|
queue.Enqueue(start);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var state = queue.Dequeue();
|
||||||
|
if (IsSolved(state.Boxes, goals))
|
||||||
|
{
|
||||||
|
return BuildResult(state, parents);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (next, dir) in Expand(state))
|
||||||
|
{
|
||||||
|
if (visited.Add(next))
|
||||||
|
{
|
||||||
|
parents[next] = (state, dir);
|
||||||
|
queue.Enqueue(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SolveResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(SolverState State, int Dir)> Expand(SolverState state)
|
||||||
|
{
|
||||||
|
var reachable = ReachableWithoutPushing(state.Player, state.Boxes);
|
||||||
|
foreach (var pos in reachable)
|
||||||
|
{
|
||||||
|
foreach (var dir in _dirs)
|
||||||
|
{
|
||||||
|
var boxPos = pos + dir;
|
||||||
|
var landing = boxPos + dir;
|
||||||
|
if (!IndexInBounds(boxPos) || !IndexInBounds(landing)) continue;
|
||||||
|
|
||||||
|
var boxIndex = Array.BinarySearch(state.Boxes, boxPos);
|
||||||
|
if (boxIndex < 0) continue;
|
||||||
|
if (_board.IsSolid(landing) || Array.BinarySearch(state.Boxes, landing) >= 0) continue;
|
||||||
|
|
||||||
|
var nextBoxes = state.Boxes.ToArray();
|
||||||
|
nextBoxes[boxIndex] = landing;
|
||||||
|
Array.Sort(nextBoxes);
|
||||||
|
yield return (new SolverState(boxPos, nextBoxes), dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SolveResult BuildResult(SolverState solved, Dictionary<SolverState, (SolverState Parent, int Dir)> parents)
|
||||||
|
{
|
||||||
|
var pushes = 0;
|
||||||
|
var turns = 0;
|
||||||
|
var branching = 0;
|
||||||
|
|
||||||
|
var path = new List<(SolverState State, int Dir)>();
|
||||||
|
var current = solved;
|
||||||
|
while (parents.TryGetValue(current, out var entry))
|
||||||
|
{
|
||||||
|
path.Add((current, entry.Dir));
|
||||||
|
current = entry.Parent;
|
||||||
|
}
|
||||||
|
path.Reverse();
|
||||||
|
|
||||||
|
var prevDir = 0;
|
||||||
|
foreach (var (_, dir) in path)
|
||||||
|
{
|
||||||
|
pushes++;
|
||||||
|
if (prevDir != 0 && dir != prevDir) turns++;
|
||||||
|
prevDir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// branching: count states along path that had more than one push option
|
||||||
|
current = solved;
|
||||||
|
while (parents.TryGetValue(current, out var entry2))
|
||||||
|
{
|
||||||
|
var pushOptions = Expand(entry2.Parent).Count();
|
||||||
|
if (pushOptions > 1) branching++;
|
||||||
|
current = entry2.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SolveResult(pushes, turns, branching);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<int> ReachableWithoutPushing(int start, int[] boxes)
|
||||||
|
{
|
||||||
|
var visited = new bool[_board.Size];
|
||||||
|
var queue = new Queue<int>();
|
||||||
|
var output = new List<int>();
|
||||||
|
|
||||||
|
if (_board.IsSolid(start) || Array.BinarySearch(boxes, start) >= 0) return output;
|
||||||
|
|
||||||
|
visited[start] = true;
|
||||||
|
queue.Enqueue(start);
|
||||||
|
output.Add(start);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var current = queue.Dequeue();
|
||||||
|
foreach (var dir in _dirs)
|
||||||
|
{
|
||||||
|
var next = current + dir;
|
||||||
|
if (!IndexInBounds(next)) continue;
|
||||||
|
if (visited[next]) continue;
|
||||||
|
if (_board.IsSolid(next) || Array.BinarySearch(boxes, next) >= 0) continue;
|
||||||
|
visited[next] = true;
|
||||||
|
queue.Enqueue(next);
|
||||||
|
output.Add(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSolved(int[] boxes, HashSet<int> goals)
|
||||||
|
{
|
||||||
|
foreach (var b in boxes)
|
||||||
|
{
|
||||||
|
if (!goals.Contains(b)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IndexInBounds(int idx) => idx >= 0 && idx < _board.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly record struct SolverState(int Player, int[] Boxes);
|
||||||
|
|
||||||
|
internal readonly record struct SolveResult(int Pushes, int Turns, int Branching)
|
||||||
|
{
|
||||||
|
public static SolveResult Fail => new(-1, -1, -1);
|
||||||
|
public bool IsFail => Pushes < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class StateComparer : IEqualityComparer<SolverState>
|
||||||
|
{
|
||||||
|
public bool Equals(SolverState x, SolverState y)
|
||||||
|
{
|
||||||
|
if (x.Player != y.Player) return false;
|
||||||
|
var a = x.Boxes;
|
||||||
|
var b = y.Boxes;
|
||||||
|
if (a.Length != b.Length) return false;
|
||||||
|
for (var i = 0; i < a.Length; i++)
|
||||||
|
{
|
||||||
|
if (a[i] != b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(SolverState obj)
|
||||||
|
{
|
||||||
|
var hash = obj.Player * 397 ^ obj.Boxes.Length;
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
for (var i = 0; i < obj.Boxes.Length; i++)
|
||||||
|
{
|
||||||
|
hash = hash * 31 + obj.Boxes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record ScrambledState(int Player, int[] Boxes);
|
||||||
1051
Program.cs
Normal file
1051
Program.cs
Normal file
File diff suppressed because it is too large
Load Diff
BIN
bin/Debug/net7.0/nekoban_map_gen
Executable file
BIN
bin/Debug/net7.0/nekoban_map_gen
Executable file
Binary file not shown.
23
bin/Debug/net7.0/nekoban_map_gen.deps.json
Normal file
23
bin/Debug/net7.0/nekoban_map_gen.deps.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v7.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v7.0": {
|
||||||
|
"nekoban_map_gen/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"nekoban_map_gen.dll": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"nekoban_map_gen/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
bin/Debug/net7.0/nekoban_map_gen.dll
Normal file
BIN
bin/Debug/net7.0/nekoban_map_gen.dll
Normal file
Binary file not shown.
BIN
bin/Debug/net7.0/nekoban_map_gen.pdb
Normal file
BIN
bin/Debug/net7.0/nekoban_map_gen.pdb
Normal file
Binary file not shown.
9
bin/Debug/net7.0/nekoban_map_gen.runtimeconfig.json
Normal file
9
bin/Debug/net7.0/nekoban_map_gen.runtimeconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net7.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
levelbalance.json
Normal file
34
levelbalance.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"bands": [
|
||||||
|
{
|
||||||
|
"startId": 3,
|
||||||
|
"endId": 20,
|
||||||
|
"boxCountLow": 1,
|
||||||
|
"boxCountHigh": 2,
|
||||||
|
"minAllowedPushes": 4,
|
||||||
|
"minAllowedTurns": 2,
|
||||||
|
"minAllowedBranching": 0,
|
||||||
|
"maskTake": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"startId": 21,
|
||||||
|
"endId": 40,
|
||||||
|
"boxCountLow": 1,
|
||||||
|
"boxCountHigh": 2,
|
||||||
|
"minAllowedPushes": 7,
|
||||||
|
"minAllowedTurns": 3,
|
||||||
|
"minAllowedBranching": 0,
|
||||||
|
"maskTake": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"startId": 41,
|
||||||
|
"endId": 60,
|
||||||
|
"boxCountLow": 2,
|
||||||
|
"boxCountHigh": 3,
|
||||||
|
"minAllowedPushes": 9,
|
||||||
|
"minAllowedTurns": 4,
|
||||||
|
"minAllowedBranching": 0,
|
||||||
|
"maskTake": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
414
mask_library.cs
Normal file
414
mask_library.cs
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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, bool includeScaled = true)
|
||||||
|
{
|
||||||
|
var seen = new HashSet<string>();
|
||||||
|
var output = new List<string[]>();
|
||||||
|
foreach (var mask in baseMasks)
|
||||||
|
{
|
||||||
|
var seeds = includeScaled ? ScaleVariants(mask) : new List<string[]> { mask };
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var scaled = new List<string[]>();
|
||||||
|
scaled.Add(mask);
|
||||||
|
var padded = Pad(mask, 1);
|
||||||
|
if (padded != null) scaled.Add(padded);
|
||||||
|
var trimmed = Pad(mask, -1);
|
||||||
|
if (trimmed != null) scaled.Add(trimmed);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
nekoban_map_gen.csproj
Normal file
10
nekoban_map_gen.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
24
nekoban_map_gen.sln
Normal file
24
nekoban_map_gen.sln
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nekoban_map_gen", "nekoban_map_gen.csproj", "{736CD653-478C-D946-DC2C-7AC55BE19A51}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{736CD653-478C-D946-DC2C-7AC55BE19A51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{736CD653-478C-D946-DC2C-7AC55BE19A51}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{736CD653-478C-D946-DC2C-7AC55BE19A51}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{736CD653-478C-D946-DC2C-7AC55BE19A51}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {1EE2C320-7A55-4F38-BAF1-71E807C9EAC5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v7.0", FrameworkDisplayName = ".NET 7.0")]
|
||||||
BIN
obj/Debug/net7.0/apphost
Executable file
BIN
obj/Debug/net7.0/apphost
Executable file
Binary file not shown.
22
obj/Debug/net7.0/nekoban_map_gen.AssemblyInfo.cs
Normal file
22
obj/Debug/net7.0/nekoban_map_gen.AssemblyInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: System.Reflection.AssemblyCompanyAttribute("nekoban_map_gen")]
|
||||||
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||||
|
[assembly: System.Reflection.AssemblyProductAttribute("nekoban_map_gen")]
|
||||||
|
[assembly: System.Reflection.AssemblyTitleAttribute("nekoban_map_gen")]
|
||||||
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
|
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
976eaa04cd48be9fbd61396f78b359e9670a79aa
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
is_global = true
|
||||||
|
build_property.TargetFramework = net7.0
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb =
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property.RootNamespace = nekoban_map_gen
|
||||||
|
build_property.ProjectDir = /Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/
|
||||||
8
obj/Debug/net7.0/nekoban_map_gen.GlobalUsings.g.cs
Normal file
8
obj/Debug/net7.0/nekoban_map_gen.GlobalUsings.g.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
global using global::System;
|
||||||
|
global using global::System.Collections.Generic;
|
||||||
|
global using global::System.IO;
|
||||||
|
global using global::System.Linq;
|
||||||
|
global using global::System.Net.Http;
|
||||||
|
global using global::System.Threading;
|
||||||
|
global using global::System.Threading.Tasks;
|
||||||
BIN
obj/Debug/net7.0/nekoban_map_gen.assets.cache
Normal file
BIN
obj/Debug/net7.0/nekoban_map_gen.assets.cache
Normal file
Binary file not shown.
BIN
obj/Debug/net7.0/nekoban_map_gen.csproj.AssemblyReference.cache
Normal file
BIN
obj/Debug/net7.0/nekoban_map_gen.csproj.AssemblyReference.cache
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
7886d3225e6b0aa6234a1c3da555a0a6b7dccd0b
|
||||||
15
obj/Debug/net7.0/nekoban_map_gen.csproj.FileListAbsolute.txt
Normal file
15
obj/Debug/net7.0/nekoban_map_gen.csproj.FileListAbsolute.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.csproj.AssemblyReference.cache
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.AssemblyInfoInputs.cache
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.AssemblyInfo.cs
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.csproj.CoreCompileInputs.cache
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/bin/Debug/net7.0/nekoban_map_gen
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/bin/Debug/net7.0/nekoban_map_gen.deps.json
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/bin/Debug/net7.0/nekoban_map_gen.runtimeconfig.json
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/bin/Debug/net7.0/nekoban_map_gen.dll
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/bin/Debug/net7.0/nekoban_map_gen.pdb
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.dll
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/refint/nekoban_map_gen.dll
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.pdb
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/nekoban_map_gen.genruntimeconfig.cache
|
||||||
|
/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/Debug/net7.0/ref/nekoban_map_gen.dll
|
||||||
BIN
obj/Debug/net7.0/nekoban_map_gen.dll
Normal file
BIN
obj/Debug/net7.0/nekoban_map_gen.dll
Normal file
Binary file not shown.
1
obj/Debug/net7.0/nekoban_map_gen.genruntimeconfig.cache
Normal file
1
obj/Debug/net7.0/nekoban_map_gen.genruntimeconfig.cache
Normal file
@@ -0,0 +1 @@
|
|||||||
|
920ad02553a0ee38d33f26c12d7e932279eb127d
|
||||||
BIN
obj/Debug/net7.0/nekoban_map_gen.pdb
Normal file
BIN
obj/Debug/net7.0/nekoban_map_gen.pdb
Normal file
Binary file not shown.
BIN
obj/Debug/net7.0/ref/nekoban_map_gen.dll
Normal file
BIN
obj/Debug/net7.0/ref/nekoban_map_gen.dll
Normal file
Binary file not shown.
BIN
obj/Debug/net7.0/refint/nekoban_map_gen.dll
Normal file
BIN
obj/Debug/net7.0/refint/nekoban_map_gen.dll
Normal file
Binary file not shown.
61
obj/nekoban_map_gen.csproj.nuget.dgspec.json
Normal file
61
obj/nekoban_map_gen.csproj.nuget.dgspec.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"format": 1,
|
||||||
|
"restore": {
|
||||||
|
"/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj": {}
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj",
|
||||||
|
"projectName": "nekoban_map_gen",
|
||||||
|
"projectPath": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj",
|
||||||
|
"packagesPath": "/Users/maximilian.j.sul/.nuget/packages/",
|
||||||
|
"outputPath": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"/Users/maximilian.j.sul/.nuget/NuGet/NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net7.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net7.0": {
|
||||||
|
"targetAlias": "net7.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net7.0": {
|
||||||
|
"targetAlias": "net7.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "/usr/local/share/dotnet/sdk/7.0.315/RuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
obj/nekoban_map_gen.csproj.nuget.g.props
Normal file
15
obj/nekoban_map_gen.csproj.nuget.g.props
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||||
|
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||||
|
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||||
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/maximilian.j.sul/.nuget/packages/</NuGetPackageRoot>
|
||||||
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/maximilian.j.sul/.nuget/packages/</NuGetPackageFolders>
|
||||||
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.6.2</NuGetToolVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<SourceRoot Include="/Users/maximilian.j.sul/.nuget/packages/" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
2
obj/nekoban_map_gen.csproj.nuget.g.targets
Normal file
2
obj/nekoban_map_gen.csproj.nuget.g.targets
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||||
66
obj/project.assets.json
Normal file
66
obj/project.assets.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"targets": {
|
||||||
|
"net7.0": {}
|
||||||
|
},
|
||||||
|
"libraries": {},
|
||||||
|
"projectFileDependencyGroups": {
|
||||||
|
"net7.0": []
|
||||||
|
},
|
||||||
|
"packageFolders": {
|
||||||
|
"/Users/maximilian.j.sul/.nuget/packages/": {}
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj",
|
||||||
|
"projectName": "nekoban_map_gen",
|
||||||
|
"projectPath": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj",
|
||||||
|
"packagesPath": "/Users/maximilian.j.sul/.nuget/packages/",
|
||||||
|
"outputPath": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/obj/",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"/Users/maximilian.j.sul/.nuget/NuGet/NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net7.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net7.0": {
|
||||||
|
"targetAlias": "net7.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net7.0": {
|
||||||
|
"targetAlias": "net7.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "/usr/local/share/dotnet/sdk/7.0.315/RuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
obj/project.nuget.cache
Normal file
8
obj/project.nuget.cache
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"dgSpecHash": "RYMvVqk+r7bCo4lc466IMiUBQNJtUPiAIecPRFNwdnBkz4B3dMDFM04zCDCH3kDAJ+g6+bpsAooJZxgB7X61oA==",
|
||||||
|
"success": true,
|
||||||
|
"projectFilePath": "/Users/maximilian.j.sul/Documents/Unity/nekoban_map_gen/nekoban_map_gen.csproj",
|
||||||
|
"expectedPackageFiles": [],
|
||||||
|
"logs": []
|
||||||
|
}
|
||||||
277
stage.json
Normal file
277
stage.json
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"grid": [
|
||||||
|
"00000000000",
|
||||||
|
"00000000000",
|
||||||
|
"00#######00",
|
||||||
|
"00##..###00",
|
||||||
|
"00#...$@#00",
|
||||||
|
"00#.....#00",
|
||||||
|
"00#...G.#00",
|
||||||
|
"00###.###00",
|
||||||
|
"00#######00",
|
||||||
|
"00000000000",
|
||||||
|
"00000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#...##0",
|
||||||
|
"#...##0",
|
||||||
|
"#G$..#0",
|
||||||
|
"#.G$.#0",
|
||||||
|
"#.#@##0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"grid": [
|
||||||
|
"000000000",
|
||||||
|
"0#######0",
|
||||||
|
"0##...##0",
|
||||||
|
"0#@$..##0",
|
||||||
|
"0#.G...#0",
|
||||||
|
"0##$..##0",
|
||||||
|
"0##.G.##0",
|
||||||
|
"0#######0",
|
||||||
|
"000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 5,
|
||||||
|
"pushLimit": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#...##0",
|
||||||
|
"#$.G##0",
|
||||||
|
"#G.$@#0",
|
||||||
|
"#..###0",
|
||||||
|
"#...##0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 6,
|
||||||
|
"pushLimit": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"grid": [
|
||||||
|
"000000000",
|
||||||
|
"0#######0",
|
||||||
|
"0###..##0",
|
||||||
|
"0#@$...#0",
|
||||||
|
"0#G....#0",
|
||||||
|
"0#.....#0",
|
||||||
|
"0###.###0",
|
||||||
|
"0#######0",
|
||||||
|
"000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#...##0",
|
||||||
|
"#..$@#0",
|
||||||
|
"#.G..#0",
|
||||||
|
"#..$##0",
|
||||||
|
"#....#0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#..@##0",
|
||||||
|
"#.#$##0",
|
||||||
|
"#....#0",
|
||||||
|
"#....#0",
|
||||||
|
"#.G.##0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"grid": [
|
||||||
|
"#######",
|
||||||
|
"#@$GG.#",
|
||||||
|
"#.$...#",
|
||||||
|
"#.....#",
|
||||||
|
"#######",
|
||||||
|
"#######",
|
||||||
|
"0000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#..@##0",
|
||||||
|
"##$$##0",
|
||||||
|
"#.G..#0",
|
||||||
|
"#G...#0",
|
||||||
|
"#...##0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 5,
|
||||||
|
"pushLimit": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"grid": [
|
||||||
|
"0000000",
|
||||||
|
"0#####0",
|
||||||
|
"0#...#0",
|
||||||
|
"0#.$.#0",
|
||||||
|
"0#G.$#0",
|
||||||
|
"0#.#@#0",
|
||||||
|
"0#####0",
|
||||||
|
"0000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"grid": [
|
||||||
|
"######0",
|
||||||
|
"#...##0",
|
||||||
|
"#..###0",
|
||||||
|
"#..$@#0",
|
||||||
|
"#$.G##0",
|
||||||
|
"#...##0",
|
||||||
|
"######0"
|
||||||
|
],
|
||||||
|
"lowestPush": 7,
|
||||||
|
"pushLimit": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"grid": [
|
||||||
|
"0######",
|
||||||
|
"0##...#",
|
||||||
|
"0##.$@#",
|
||||||
|
"0#.G..#",
|
||||||
|
"0##G$##",
|
||||||
|
"0##...#",
|
||||||
|
"0######"
|
||||||
|
],
|
||||||
|
"lowestPush": 5,
|
||||||
|
"pushLimit": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"grid": [
|
||||||
|
"00000000000",
|
||||||
|
"00000000000",
|
||||||
|
"00#######00",
|
||||||
|
"00##...##00",
|
||||||
|
"00##.$.##00",
|
||||||
|
"00#@$.G.#00",
|
||||||
|
"00##..G##00",
|
||||||
|
"00##...##00",
|
||||||
|
"00#######00",
|
||||||
|
"00000000000",
|
||||||
|
"00000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 5,
|
||||||
|
"pushLimit": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"grid": [
|
||||||
|
"0######",
|
||||||
|
"0##.G.#",
|
||||||
|
"0##$.G#",
|
||||||
|
"0#....#",
|
||||||
|
"0#@$.##",
|
||||||
|
"0##...#",
|
||||||
|
"0######"
|
||||||
|
],
|
||||||
|
"lowestPush": 8,
|
||||||
|
"pushLimit": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"grid": [
|
||||||
|
"0######",
|
||||||
|
"0##...#",
|
||||||
|
"0#...##",
|
||||||
|
"0#....#",
|
||||||
|
"0##$.G#",
|
||||||
|
"0##@..#",
|
||||||
|
"0######"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"grid": [
|
||||||
|
"00000000000",
|
||||||
|
"00000000000",
|
||||||
|
"00#######00",
|
||||||
|
"00##...##00",
|
||||||
|
"00##G.$##00",
|
||||||
|
"00#.G...#00",
|
||||||
|
"00#...$.#00",
|
||||||
|
"00##..@##00",
|
||||||
|
"00#######00",
|
||||||
|
"00000000000",
|
||||||
|
"00000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 7,
|
||||||
|
"pushLimit": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"grid": [
|
||||||
|
"000000000",
|
||||||
|
"0#######0",
|
||||||
|
"0##...##0",
|
||||||
|
"0##G..##0",
|
||||||
|
"0#.G.$@#0",
|
||||||
|
"0##.$.##0",
|
||||||
|
"0##...##0",
|
||||||
|
"0#######0",
|
||||||
|
"000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 5,
|
||||||
|
"pushLimit": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"grid": [
|
||||||
|
"00000000000",
|
||||||
|
"00000000000",
|
||||||
|
"00#######00",
|
||||||
|
"00###@###00",
|
||||||
|
"00#..$..#00",
|
||||||
|
"00#...$.#00",
|
||||||
|
"00#.G.G.#00",
|
||||||
|
"00###.###00",
|
||||||
|
"00#######00",
|
||||||
|
"00000000000",
|
||||||
|
"00000000000"
|
||||||
|
],
|
||||||
|
"lowestPush": 4,
|
||||||
|
"pushLimit": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user