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(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 cells = ConvertCells(source.CMS); GetBounds(cells, out int minX, out int maxX, out int minY, out int maxY); List pieces = new List(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 doors = new List(source.DMS.Length); foreach (DoorDto door in source.DMS) { List positions = ConvertPositions(door.BPMS); doors.Add(new DoorDefinition(door.BCT, positions, ResolveDoorSide(positions, minX, maxX, minY, maxY), door.DIC)); } var curtains = new List(); if (source.CLMS != null) foreach (CurtainDto curtain in source.CLMS) curtains.Add(new CurtainLockDefinition(ConvertPositions(curtain.BPMS), curtain.CLC)); List walls = source.WMS == null ? new List() : 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 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 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 ConvertCells(CellDto[] values) { List result = new List(values.Length); var unique = new HashSet(); foreach (CellDto value in values) { GridPosition position = Convert(value.BPM); if (unique.Add(position)) result.Add(position); } return result; } private static List ConvertWalls(WallDto[] values) { List result = new List(values.Length); var unique = new HashSet(); foreach (WallDto value in values) { GridPosition position = Convert(value.BPM); if (unique.Add(position)) result.Add(position); } return result; } private static List ConvertPositions(GridPositionDto[] values) { if (values == null || values.Length == 0) throw new FormatException("Piece or door contains no positions."); List result = new List(values.Length); var unique = new HashSet(); 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; } } }