Files
blockout/BlockOutReplica/Assets/Scripts/Infrastructure/OriginalLevelJsonParser.cs
T
2026-10-20 13:21:04 +08:00

128 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using BlockOutReplica.Domain;
using UnityEngine;
namespace BlockOutReplica.Infrastructure
{
public static class OriginalLevelJsonParser
{
public static LevelDefinition Parse(string json)
{
if (string.IsNullOrWhiteSpace(json))
throw new ArgumentException("Original level JSON is empty.", nameof(json));
// Bundled t238-level-19 uses X/Y while neighboring seeds use x/y.
// Normalize property names, not numeric values: zero remains valid
// and mixed-case coordinates cannot be mistaken for missing data.
json = Regex.Replace(json, "\"([XY])\"\\s*:", match => "\"" + match.Groups[1].Value.ToLowerInvariant() + "\":");
OriginalLevelDto source = JsonUtility.FromJson<OriginalLevelDto>(json);
if (source == null || source.BMS == null || source.DMS == null || source.CMS == null)
throw new FormatException("Original level JSON is missing BMS, DMS or CMS.");
if (source.CMS.Length == 0)
throw new FormatException("Original level contains no board cells.");
List<GridPosition> cells = ConvertCells(source.CMS);
GetBounds(cells, out int minX, out int maxX, out int minY, out int maxY);
List<PieceDefinition> pieces = new List<PieceDefinition>(source.BMS.Length);
foreach (BlockDto block in source.BMS)
pieces.Add(new PieceDefinition(block.BCT, ConvertPositions(block.BPMS), block.BIC, block.ILE, block.LBCT));
List<DoorDefinition> doors = new List<DoorDefinition>(source.DMS.Length);
foreach (DoorDto door in source.DMS)
{
List<GridPosition> positions = ConvertPositions(door.BPMS);
doors.Add(new DoorDefinition(door.BCT, positions,
ResolveDoorSide(positions, minX, maxX, minY, maxY), door.DIC));
}
var curtains = new List<CurtainLockDefinition>();
if (source.CLMS != null)
foreach (CurtainDto curtain in source.CLMS)
curtains.Add(new CurtainLockDefinition(ConvertPositions(curtain.BPMS), curtain.CLC));
List<GridPosition> walls = source.WMS == null
? new List<GridPosition>() : ConvertWalls(source.WMS);
GridPosition start = source.BSP != null && source.BSP.Length > 0
? Convert(source.BSP[0]) : new GridPosition(minX - 1, minY - 1);
return new LevelDefinition(pieces, doors, walls, cells, start,
minX, maxX, minY, maxY, curtains);
}
private static DoorSide ResolveDoorSide(IReadOnlyList<GridPosition> positions,
int minX, int maxX, int minY, int maxY)
{
float x = 0f, y = 0f;
for (int i = 0; i < positions.Count; i++) { x += positions[i].X; y += positions[i].Y; }
x /= positions.Count; y /= positions.Count;
if (y > maxY) return DoorSide.Top;
if (y < minY) return DoorSide.Bottom;
if (x < minX) return DoorSide.Left;
if (x > maxX) return DoorSide.Right;
throw new FormatException("Door is not outside the board boundary.");
}
private static void GetBounds(IReadOnlyList<GridPosition> cells,
out int minX, out int maxX, out int minY, out int maxY)
{
minX = maxX = cells[0].X; minY = maxY = cells[0].Y;
for (int i = 1; i < cells.Count; i++)
{
minX = Mathf.Min(minX, cells[i].X); maxX = Mathf.Max(maxX, cells[i].X);
minY = Mathf.Min(minY, cells[i].Y); maxY = Mathf.Max(maxY, cells[i].Y);
}
}
private static List<GridPosition> ConvertCells(CellDto[] values)
{
List<GridPosition> result = new List<GridPosition>(values.Length);
var unique = new HashSet<GridPosition>();
foreach (CellDto value in values)
{
GridPosition position = Convert(value.BPM);
if (unique.Add(position)) result.Add(position);
}
return result;
}
private static List<GridPosition> ConvertWalls(WallDto[] values)
{
List<GridPosition> result = new List<GridPosition>(values.Length);
var unique = new HashSet<GridPosition>();
foreach (WallDto value in values)
{
GridPosition position = Convert(value.BPM);
if (unique.Add(position)) result.Add(position);
}
return result;
}
private static List<GridPosition> ConvertPositions(GridPositionDto[] values)
{
if (values == null || values.Length == 0)
throw new FormatException("Piece or door contains no positions.");
List<GridPosition> result = new List<GridPosition>(values.Length);
var unique = new HashSet<GridPosition>();
foreach (GridPositionDto value in values)
{
GridPosition position = Convert(value);
if (unique.Add(position)) result.Add(position);
}
return result;
}
private static GridPosition Convert(GridPositionDto value)
{
if (value == null) throw new FormatException("Level contains a null grid position.");
return new GridPosition(value.x, value.y);
}
[Serializable] private sealed class OriginalLevelDto
{ public BlockDto[] BMS; public DoorDto[] DMS; public WallDto[] WMS; public CellDto[] CMS; public GridPositionDto[] BSP; public CurtainDto[] CLMS; }
[Serializable] private sealed class BlockDto
{ public int BCT; public GridPositionDto[] BPMS; public int BIC; public bool ILE; public int LBCT; }
[Serializable] private sealed class DoorDto { public int BCT; public GridPositionDto[] BPMS; public int DIC; }
[Serializable] private sealed class CurtainDto { public GridPositionDto[] BPMS; public int CLC; }
[Serializable] private sealed class WallDto { public GridPositionDto BPM; }
[Serializable] private sealed class CellDto { public GridPositionDto BPM; }
[Serializable] private sealed class GridPositionDto { public int x; public int y; }
}
}