Chessistics/chessistics-engine/Loading/LevelLoader.cs
Samuel Bouchet a7280b1a5a Overhaul turn mechanics, collision destruction, and visual animations
- New turn order: produce -> transfer -> move -> collision resolution
- Collisions now destroy weaker pieces (status > level > mutual destruction)
  instead of halting the simulation. SimPhase.Collision removed.
- Add piece Level property (all level 1 in proto, prepared for future)
- Production fires every turn (interval concept removed), buffer = Amount
  (default 1, future 2-4), leftovers overwritten each turn
- Transfer tiebreaker: status > level > clockwise direction (alternating
  even/odd turns in y-up coords), replaces distance-to-production
- Demands always accept matching cargo even when already satisfied
- TurnNumber added to all turn events for animation grouping
- Simultaneous animations: produce flash, cargo slide, parallel piece moves
- Camera centering fix + middle-click pan
- GDD updated with new rules + lore section added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:44:12 +02:00

120 lines
3.9 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using Chessistics.Engine.Model;
namespace Chessistics.Engine.Loading;
public static class LevelLoader
{
private static readonly JsonSerializerOptions Options = new()
{
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
public static LevelDef Load(string json)
{
var dto = JsonSerializer.Deserialize<LevelDto>(json, Options)
?? throw new JsonException("Failed to deserialize level JSON.");
Validate(dto);
return new LevelDef
{
Id = dto.Id,
Name = dto.Name,
Description = dto.Description ?? "",
Width = dto.Width,
Height = dto.Height,
Productions = dto.Productions.Select(p => new ProductionDef(
new Coords(p.Col, p.Row), p.Name, ParseCargo(p.Cargo), p.Amount
)).ToList(),
Demands = dto.Demands.Select(d => new DemandDef(
new Coords(d.Col, d.Row), d.Name, ParseCargo(d.Cargo), d.Amount, d.Deadline
)).ToList(),
Walls = dto.Walls?.Select(w => new Coords(w.Col, w.Row)).ToList() ?? [],
Stock = dto.Stock.Select(s => new PieceStock(ParseKind(s.Kind), s.Count, s.Level)).ToList()
};
}
public static LevelDef LoadFromFile(string path)
{
var json = File.ReadAllText(path);
return Load(json);
}
private static CargoType ParseCargo(string cargo) => cargo.ToLowerInvariant() switch
{
"wood" => CargoType.Wood,
"stone" => CargoType.Stone,
_ => throw new JsonException($"Unknown cargo type: '{cargo}'")
};
private static PieceKind ParseKind(string kind) => kind.ToLowerInvariant() switch
{
"rook" => PieceKind.Rook,
"bishop" => PieceKind.Bishop,
"knight" => PieceKind.Knight,
_ => throw new JsonException($"Unknown piece kind: '{kind}'")
};
private static void Validate(LevelDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name))
throw new JsonException("Level name is required.");
if (dto.Width <= 0 || dto.Height <= 0)
throw new JsonException("Level dimensions must be positive.");
if (dto.Productions.Count == 0)
throw new JsonException("Level must have at least one production.");
if (dto.Demands.Count == 0)
throw new JsonException("Level must have at least one demand.");
if (dto.Stock.Count == 0)
throw new JsonException("Level must have at least one piece in stock.");
}
// DTOs for JSON deserialization
private class LevelDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string? Description { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public List<ProductionDto> Productions { get; set; } = [];
public List<DemandDto> Demands { get; set; } = [];
public List<CoordsDto>? Walls { get; set; }
public List<StockDto> Stock { get; set; } = [];
}
private class ProductionDto
{
public int Col { get; set; }
public int Row { get; set; }
public string Name { get; set; } = "";
public string Cargo { get; set; } = "";
public int Amount { get; set; } = 1;
}
private class DemandDto
{
public int Col { get; set; }
public int Row { get; set; }
public string Name { get; set; } = "";
public string Cargo { get; set; } = "";
public int Amount { get; set; }
public int Deadline { get; set; }
}
private class CoordsDto
{
public int Col { get; set; }
public int Row { get; set; }
}
private class StockDto
{
public string Kind { get; set; } = "";
public int Count { get; set; }
public int Level { get; set; } = 1;
}
}