using Chessistics.Engine.Commands; using Chessistics.Engine.Loading; using Chessistics.Engine.Model; using Chessistics.Engine.Simulation; using Xunit; namespace Chessistics.Tests.Loading; /// /// Validates campaign_01.json structural integrity: /// no cell overlaps, unique building names, walls only on new cells. /// public class CampaignValidationTests { private static CampaignDef LoadCampaign() => CampaignLoader.LoadFromFile("../../../../Data/campaigns/campaign_01.json"); [Fact] public void Campaign01_NoBuildingOverlaps() { var campaign = LoadCampaign(); var sim = new GameSim(campaign); sim.ProcessCommand(new LoadCampaignCommand()); // Step through missions by manually applying patches and checking for conflicts var state = BoardState.FromCampaign(campaign); // Track all active buildings after each mission for (int i = 0; i < campaign.Missions.Count; i++) { var mission = campaign.Missions[i]; state.ApplyTerrainPatch(mission.TerrainPatch, i); // After each mission, verify no cell has multiple building types foreach (var pos in state.Productions.Keys) { Assert.False(state.Demands.ContainsKey(pos), $"Mission {i + 1}: Production and Demand overlap at {pos}"); Assert.False(state.Transformers.ContainsKey(pos), $"Mission {i + 1}: Production and Transformer overlap at {pos}"); Assert.NotEqual(CellType.Wall, state.GetCell(pos)); } foreach (var pos in state.Demands.Keys) { Assert.False(state.Transformers.ContainsKey(pos), $"Mission {i + 1}: Demand and Transformer overlap at {pos}"); Assert.NotEqual(CellType.Wall, state.GetCell(pos)); } foreach (var pos in state.Transformers.Keys) { Assert.NotEqual(CellType.Wall, state.GetCell(pos)); } } } [Fact] public void Campaign01_UniqueBuildingNames() { var campaign = LoadCampaign(); var state = BoardState.FromCampaign(campaign); for (int i = 0; i < campaign.Missions.Count; i++) { state.ApplyTerrainPatch(campaign.Missions[i].TerrainPatch, i); } // Collect all building names var names = new List(); names.AddRange(state.Productions.Values.Select(p => p.Name)); names.AddRange(state.Demands.Values.Select(d => d.Name)); names.AddRange(state.Transformers.Values.Select(t => t.Name)); var duplicates = names.GroupBy(n => n).Where(g => g.Count() > 1).Select(g => g.Key).ToList(); Assert.Empty(duplicates); } [Fact] public void Campaign01_WallsOnlyOnNewOrEmptyCells() { var campaign = LoadCampaign(); // Track which cells have buildings before each mission var buildingCells = new HashSet(); int prevWidth = campaign.InitialWidth; int prevHeight = campaign.InitialHeight; for (int i = 0; i < campaign.Missions.Count; i++) { var mission = campaign.Missions[i]; // Check walls in this mission's patch foreach (var cell in mission.TerrainPatch.Cells) { if (cell.Type == CellType.Wall) { // Wall should be on a cell that was either: // - Outside the previous board dimensions (new cell) // - Not a building cell bool isNewCell = cell.Col >= prevWidth || cell.Row >= prevHeight; bool isBuilding = buildingCells.Contains(new Coords(cell.Col, cell.Row)); Assert.True(isNewCell || !isBuilding, $"Mission {i + 1}: Wall at ({cell.Col},{cell.Row}) overwrites an existing building"); } } // Update building tracking foreach (var cell in mission.TerrainPatch.Cells) { var coords = new Coords(cell.Col, cell.Row); if (cell.Type is CellType.Production or CellType.Demand or CellType.Transformer) buildingCells.Add(coords); else buildingCells.Remove(coords); } prevWidth = mission.TerrainPatch.NewWidth; prevHeight = mission.TerrainPatch.NewHeight; } } [Fact] public void Campaign01_AllMissionsHaveFlavor() { var campaign = LoadCampaign(); for (int i = 0; i < campaign.Missions.Count; i++) { Assert.False(string.IsNullOrWhiteSpace(campaign.Missions[i].Flavor), $"Mission {i + 1} ({campaign.Missions[i].Name}) has no flavor text"); } } }