diff --git a/Data/campaigns/campaign_01.json b/Data/campaigns/campaign_01.json index d661c9b..f8b33d3 100644 --- a/Data/campaigns/campaign_01.json +++ b/Data/campaigns/campaign_01.json @@ -204,8 +204,8 @@ }, { "id": 7, - "name": "Le Couronnement", - "description": "Le Comptoir transforme les outils en or. Livrez l'or au Trésor Royal pour couronner le Roi.", + "name": "Le Comptoir", + "description": "Le Comptoir transforme les outils en or. Livrez l'or au Trésor Royal — bientôt le Roi.", "flavor": "« De l'or ! Le Roi sera content. Enfin… s'il reste du budget pour nous payer. » — Dame sceptique", "terrainPatch": { "newWidth": 10, @@ -222,6 +222,91 @@ { "kind": "rook", "count": 4 }, { "kind": "bishop", "count": 2 } ] + }, + { + "id": 8, + "name": "L'Expansion Finale", + "description": "Deux nouveaux transformateurs s'ajoutent pour soutenir la production. Les routes existantes doivent tenir.", + "flavor": "« On double l'équipe, on double la paie ? Non. D'accord, double le boulot alors. » — Pion pragmatique", + "terrainPatch": { + "newWidth": 12, + "newHeight": 10, + "cells": [ + { "col": 10, "row": 0, "type": "empty" }, + { "col": 10, "row": 1, "type": "empty" }, + { "col": 10, "row": 2, "type": "empty" }, + { "col": 10, "row": 3, "type": "empty" }, + { "col": 10, "row": 4, "type": "empty" }, + { "col": 10, "row": 5, "type": "transformer", "transformer": { "name": "Forge Est", "inputCargo": "wood", "inputRequired": 2, "outputCargo": "tools", "outputAmount": 1 } }, + { "col": 10, "row": 6, "type": "wall" }, + { "col": 10, "row": 7, "type": "empty" }, + { "col": 10, "row": 8, "type": "empty" }, + { "col": 10, "row": 9, "type": "empty" }, + { "col": 11, "row": 0, "type": "empty" }, + { "col": 11, "row": 1, "type": "empty" }, + { "col": 11, "row": 2, "type": "empty" }, + { "col": 11, "row": 3, "type": "transformer", "transformer": { "name": "Armurerie Est", "inputCargo": "stone", "inputRequired": 2, "outputCargo": "arms", "outputAmount": 1 } }, + { "col": 11, "row": 4, "type": "empty" }, + { "col": 11, "row": 5, "type": "empty" }, + { "col": 11, "row": 6, "type": "empty" }, + { "col": 11, "row": 7, "type": "wall" }, + { "col": 11, "row": 8, "type": "empty" }, + { "col": 11, "row": 9, "type": "demand", "demand": { "name": "Entrepôt Est", "cargo": "tools", "amount": 2 } } + ] + }, + "unlockedPieces": [], + "unlockedLevels": [], + "stock": [ + { "kind": "rook", "count": 4 }, + { "kind": "bishop", "count": 2 }, + { "kind": "knight", "count": 2 }, + { "kind": "pawn", "count": 4 } + ] + }, + { + "id": 9, + "name": "Le Couronnement", + "description": "La Cathédrale est la dernière étape. Elle réclame outils, armes et or simultanément pour couronner le Roi.", + "flavor": "« Trois offrandes, un Roi. Et après, c'est lui qui nous couronne ? Ou qui nous exécute ? » — Cavalier inquiet", + "terrainPatch": { + "newWidth": 12, + "newHeight": 12, + "cells": [ + { "col": 0, "row": 10, "type": "empty" }, + { "col": 0, "row": 11, "type": "empty" }, + { "col": 1, "row": 10, "type": "empty" }, + { "col": 1, "row": 11, "type": "empty" }, + { "col": 2, "row": 10, "type": "empty" }, + { "col": 2, "row": 11, "type": "empty" }, + { "col": 3, "row": 10, "type": "empty" }, + { "col": 3, "row": 11, "type": "empty" }, + { "col": 4, "row": 10, "type": "empty" }, + { "col": 4, "row": 11, "type": "empty" }, + { "col": 5, "row": 10, "type": "empty" }, + { "col": 5, "row": 11, "type": "demand", "demand": { "name": "Cathédrale (Outils)", "cargo": "tools", "amount": 2 } }, + { "col": 6, "row": 10, "type": "empty" }, + { "col": 6, "row": 11, "type": "demand", "demand": { "name": "Cathédrale (Armes)", "cargo": "arms", "amount": 2 } }, + { "col": 7, "row": 10, "type": "empty" }, + { "col": 7, "row": 11, "type": "demand", "demand": { "name": "Cathédrale (Or)", "cargo": "gold", "amount": 2 } }, + { "col": 8, "row": 10, "type": "empty" }, + { "col": 8, "row": 11, "type": "empty" }, + { "col": 9, "row": 10, "type": "empty" }, + { "col": 9, "row": 11, "type": "empty" }, + { "col": 10, "row": 10, "type": "empty" }, + { "col": 10, "row": 11, "type": "empty" }, + { "col": 11, "row": 10, "type": "empty" }, + { "col": 11, "row": 11, "type": "empty" } + ] + }, + "unlockedPieces": [], + "unlockedLevels": [], + "stock": [ + { "kind": "queen", "count": 2 }, + { "kind": "rook", "count": 4 }, + { "kind": "bishop", "count": 2 }, + { "kind": "knight", "count": 2 }, + { "kind": "pawn", "count": 4 } + ] } ] } diff --git a/chessistics-tests/Loading/Campaign01Tests.cs b/chessistics-tests/Loading/Campaign01Tests.cs new file mode 100644 index 0000000..2d3c990 --- /dev/null +++ b/chessistics-tests/Loading/Campaign01Tests.cs @@ -0,0 +1,81 @@ +using System.IO; +using Chessistics.Engine.Loading; +using Chessistics.Engine.Model; +using Xunit; + +namespace Chessistics.Tests.Loading; + +public class Campaign01Tests +{ + private static CampaignDef LoadRealCampaign() + { + var repoRoot = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", ".."); + var path = Path.GetFullPath(Path.Combine(repoRoot, "Data", "campaigns", "campaign_01.json")); + return CampaignLoader.Load(File.ReadAllText(path)); + } + + [Fact] + public void Campaign_HasExpectedStructure() + { + var c = LoadRealCampaign(); + Assert.Equal(9, c.Missions.Count); + Assert.Equal(4, c.InitialWidth); + Assert.Equal(4, c.InitialHeight); + } + + [Fact] + public void Mission8_AddsTwoTransformersAndExpandsTo12x10() + { + var c = LoadRealCampaign(); + var m8 = c.Missions[7]; + Assert.Equal("L'Expansion Finale", m8.Name); + Assert.Equal(12, m8.TerrainPatch.NewWidth); + Assert.Equal(10, m8.TerrainPatch.NewHeight); + + var transformers = m8.TerrainPatch.Cells.Where(p => p.Type == CellType.Transformer).ToList(); + Assert.Equal(2, transformers.Count); + Assert.Contains(transformers, t => t.Transformer!.Name == "Forge Est"); + Assert.Contains(transformers, t => t.Transformer!.Name == "Armurerie Est"); + } + + [Fact] + public void Mission9_CathedralDemandsAllThreeCargoTypes() + { + var c = LoadRealCampaign(); + var m9 = c.Missions[8]; + Assert.Equal("Le Couronnement", m9.Name); + Assert.Equal(12, m9.TerrainPatch.NewWidth); + Assert.Equal(12, m9.TerrainPatch.NewHeight); + + var demands = m9.TerrainPatch.Cells.Where(p => p.Type == CellType.Demand).ToList(); + Assert.Equal(3, demands.Count); + var types = demands.Select(d => d.Demand!.Cargo).ToHashSet(); + Assert.Contains(CargoType.Tools, types); + Assert.Contains(CargoType.Arms, types); + Assert.Contains(CargoType.Gold, types); + } + + [Fact] + public void Mission7_RenamedToComptoir() + { + var c = LoadRealCampaign(); + var m7 = c.Missions[6]; + Assert.Equal("Le Comptoir", m7.Name); + } + + [Fact] + public void AllTerrainPatchesAreNonRegressive() + { + // Subsequent missions must only grow the board, never shrink it. + var c = LoadRealCampaign(); + int w = c.InitialWidth, h = c.InitialHeight; + for (int i = 0; i < c.Missions.Count; i++) + { + var tp = c.Missions[i].TerrainPatch; + Assert.True(tp.NewWidth >= w, $"Mission {i}: width shrunk from {w} to {tp.NewWidth}"); + Assert.True(tp.NewHeight >= h, $"Mission {i}: height shrunk from {h} to {tp.NewHeight}"); + w = tp.NewWidth; + h = tp.NewHeight; + } + } +} diff --git a/chessistics-tests/Loading/CampaignFileTests.cs b/chessistics-tests/Loading/CampaignFileTests.cs index 814f969..602034a 100644 --- a/chessistics-tests/Loading/CampaignFileTests.cs +++ b/chessistics-tests/Loading/CampaignFileTests.cs @@ -12,7 +12,7 @@ public class CampaignFileTests var campaign = CampaignLoader.LoadFromFile("../../../../Data/campaigns/campaign_01.json"); Assert.Equal("La Quête du Roi", campaign.Name); - Assert.Equal(7, campaign.Missions.Count); + Assert.Equal(9, campaign.Missions.Count); } [Fact] @@ -52,7 +52,7 @@ public class CampaignFileTests var campaign = CampaignLoader.LoadFromFile("../../../../Data/campaigns/campaign_01.json"); var m7 = campaign.Missions[6]; - Assert.Equal("Le Couronnement", m7.Name); + Assert.Equal("Le Comptoir", m7.Name); var transformerCell = m7.TerrainPatch.Cells .FirstOrDefault(c => c.Type == CellType.Transformer); diff --git a/docs/PLAN.md b/docs/PLAN.md index 8a076d2..036e3a4 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -12,12 +12,6 @@ et l'extension de la campagne. Le moteur expose deja les commandes et events requis ; cote Godot il manque les surfaces d'interaction et d'animation. -### 1.6 Visualisation des trajets -`TrajectView` existe. Manque : -- Fleches directionnelles sur le trait. -- Pulsation legere du trait quand la piece est en mouvement. -- Couleur unique par piece pour distinguer les chaines. - --- ## 2. Extension de la campagne