diff --git a/LICENSE.md b/LICENSE.md index 1e4cab6..731cf7d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -7,8 +7,9 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see . -This program uses the following third party assets: +This program uses the following third party assets and content: * Diplomacy Adjudicator Test Cases: Copyright (C) 2001-2024 Lucas B. Kruijswijk * Diplomacy standard map: Creative Commons BY-SA 3.0 Copyright (C) 2009 Martin Asal +* Multiversal Diplomacy Adjudicator Test Cases: GNU General Public License v3.0 Copyright (C) 2022-2024 Tim Van Baak * The Time Machinations: Copyright (C) 2024 Matthew Pickard, AKA Lady Razor \ No newline at end of file diff --git a/server/Adjudication/Execution/Executor.cs b/server/Adjudication/Execution/Executor.cs index 1f8d698..882f32f 100644 --- a/server/Adjudication/Execution/Executor.cs +++ b/server/Adjudication/Execution/Executor.cs @@ -80,7 +80,7 @@ private Board AdvanceMajorBoard(Board previousBoard) && previousBoard.Contains(o.Location) && !originalRetreatingUnits.Contains(o.Unit!)); var incomingMoves = world.Orders.OfType().Where(m => - m.Status == OrderStatus.Success || m.Status == OrderStatus.Retreat + (m.Status == OrderStatus.Success || m.Status == OrderStatus.Retreat) && previousBoard.Contains(m.Destination)); var units = new List(); diff --git a/server/Adjudication/Validation/AdjacencyValidator.cs b/server/Adjudication/Validation/AdjacencyValidator.cs index d1df988..d752756 100644 --- a/server/Adjudication/Validation/AdjacencyValidator.cs +++ b/server/Adjudication/Validation/AdjacencyValidator.cs @@ -59,6 +59,11 @@ public bool IsValidIntraBoardMove(Unit unit, Location location, Location destina return false; } + if (location.RegionId == destination.RegionId) + { + return true; + } + var locationRegion = regions.First(r => r.Id == location.RegionId); var destinationRegion = regions.First(r => r.Id == destination.RegionId); diff --git a/server/Tests/MDATC_A.cs b/server/Tests/MDATC_A.cs new file mode 100644 index 0000000..e88506c --- /dev/null +++ b/server/Tests/MDATC_A.cs @@ -0,0 +1,291 @@ +using Adjudication; +using Entities; +using Enums; +using FluentAssertions; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Tests; + +// Adapted and extended from Multiversal Diplomacy Adjudicator Test Cases, Tim Van Baak +// https://github.com/Jaculabilis/5dplomacy/blob/master/MDATC.html + +[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] +public class MDATC_A : AdjudicationTestBase +{ + [Fact(DisplayName = "A.01. Move to same region with loose adjacencies")] + public void MDATC_A_1() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = topBoard.AddUnits([(Nation.England, UnitType.Army, "Lon")]); + + var order = units.Get("Lon").Move("Lon", timeline: 2); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Success); + + topBoard.Next().ShouldHaveUnits([]); + bottomBoard.Next().ShouldHaveUnits([(Nation.England, UnitType.Army, "Lon", false)]); + } + + [Fact(DisplayName = "A.02. Move to same region with strict adjacencies")] + public void MDATC_A_2() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = topBoard.AddUnits([(Nation.Turkey, UnitType.Fleet, "Smy")]); + + var order = units.Get("Smy").Move("Smy", timeline: 2); + + // Act + new Adjudicator(world, true, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Success); + + topBoard.Next().ShouldHaveUnits([]); + bottomBoard.Next().ShouldHaveUnits([(Nation.Turkey, UnitType.Fleet, "Smy", false)]); + } + + [Fact(DisplayName = "A.03. Move to neighbouring region with loose adjacencies")] + public void MDATC_A_3() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = bottomBoard.AddUnits([(Nation.Austria, UnitType.Army, "Vie")]); + + var order = units.Get("Vie", timeline: 2).Move("Bud"); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Success); + + topBoard.Next().ShouldHaveUnits([(Nation.Austria, UnitType.Army, "Bud", false)]); + bottomBoard.Next().ShouldHaveUnits([]); + } + + [Fact(DisplayName = "A.04. Move to neighbouring region with strict adjacencies")] + public void MDATC_A_4() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = bottomBoard.AddUnits([(Nation.Italy, UnitType.Fleet, "Tun")]); + + var order = units.Get("Tun", timeline: 2).Move("WES"); + + // Act + new Adjudicator(world, true, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + topBoard.Next().ShouldHaveUnits([]); + bottomBoard.Next().ShouldHaveUnits([(Nation.Italy, UnitType.Fleet, "Tun", false)]); + } + + [Fact(DisplayName = "A.05. Move to non-neighbouring region")] + public void MDATC_A_5() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = topBoard.AddUnits([(Nation.England, UnitType.Fleet, "Lvp")]); + + var order = units.Get("Lvp").Move("Edi", timeline: 2); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + topBoard.Next().ShouldHaveUnits([(Nation.England, UnitType.Fleet, "Lvp", false)]); + bottomBoard.Next().ShouldHaveUnits([]); + } + + [Fact(DisplayName = "A.06. Can't cross coasts across time")] + public void MDATC_A_6() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = bottomBoard.AddUnits([(Nation.France, UnitType.Fleet, "Spa_S")]); + + var order = units.Get("Spa_S", timeline: 2).Move("Spa_N"); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + topBoard.Next().ShouldHaveUnits([]); + bottomBoard.Next().ShouldHaveUnits([(Nation.France, UnitType.Fleet, "Spa_S", false)]); + } + + [Fact(DisplayName = "A.07. No diagonal board movement")] + public void MDATC_A_7() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(phase: Phase.Fall); + var bottomBoard = world.AddBoard(timeline: 2); + + var units = topBoard.AddUnits([(Nation.Germany, UnitType.Army, "Mun")]); + + var order = units.Get("Mun", phase: Phase.Fall).Move("Mun", timeline: 2); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + topBoard.Next().ShouldHaveUnits([(Nation.Germany, UnitType.Army, "Mun", false)]); + bottomBoard.Next().ShouldHaveUnits([]); + } + + [Fact(DisplayName = "A.08. Must be adjacent timeline without convoy")] + public void MDATC_A_8() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(); + var middleBoard = world.AddBoard(timeline: 2); + var bottomBoard = world.AddBoard(timeline: 3); + + var units = topBoard.AddUnits([(Nation.England, UnitType.Army, "Lon")]); + + var order = units.Get("Lon").Move("Lon", timeline: 3); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + topBoard.Next().ShouldHaveUnits([(Nation.England, UnitType.Army, "Lon", false)]); + middleBoard.Next().ShouldHaveUnits([]); + bottomBoard.Next().ShouldHaveUnits([]); + } + + [Fact(DisplayName = "A.09. Must be immediate past major board without convoy")] + public void MDATC_A_9() + { + // Arrange + var world = new World(); + + var presentBoard = world.AddBoard(year: 1902); + var pastBoard1 = world.AddBoard(phase: Phase.Winter); + var pastBoard2 = world.AddBoard(phase: Phase.Fall); + var pastBoard3 = world.AddBoard(); + + var units = presentBoard.AddUnits( + [ + (Nation.England, UnitType.Army, "Lon"), + (Nation.France, UnitType.Army, "Par"), + ]); + + var englishMove = units.Get("Lon", year: 1902).Move("Lon"); + var frenchMove = units.Get("Par", year: 1902).Move("Gas", phase: Phase.Fall); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + englishMove.Status.Should().Be(OrderStatus.Invalid); + frenchMove.Status.Should().Be(OrderStatus.Success); + + presentBoard.Next().ShouldHaveUnits([(Nation.England, UnitType.Army, "Lon", false)]); + pastBoard1.ShouldNotHaveNextBoard(timeline: 2); + pastBoard2.Next(timeline: 2).ShouldHaveUnits([(Nation.France, UnitType.Army, "Gas", false)]); + pastBoard3.ShouldNotHaveNextBoard(timeline: 2); + } + + [Fact(DisplayName = "A.10. No move to winter board")] + public void MDATC_A_10() + { + // Arrange + var world = new World(); + + var presentBoard = world.AddBoard(year: 1902); + var pastBoard = world.AddBoard(phase: Phase.Winter); + + presentBoard.AddCentres([(Nation.Russia, "Sev")]); + pastBoard.AddCentres([(Nation.Russia, "Sev")]); + + List units = + [ + .. pastBoard.AddUnits([(Nation.Russia, UnitType.Fleet, "Sev")]), + .. presentBoard.AddUnits([(Nation.Russia, UnitType.Fleet, "Sev")] + )]; + + var order = units.Get("Sev", year: 1902).Move("Sev", phase: Phase.Winter); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + order.Status.Should().Be(OrderStatus.Invalid); + + presentBoard.Next().ShouldHaveUnits([(Nation.Russia, UnitType.Fleet, "Sev", false)]); + pastBoard.ShouldNotHaveNextBoard(timeline: 2); + } + + [Fact(DisplayName = "A.11. Simultaneous adjustment and movement phases advance together")] + public void MDATC_A_11() + { + // Arrange + var world = new World(); + + var topBoard = world.AddBoard(year: 1902); + var bottomBoard = world.AddBoard(timeline: 2, phase: Phase.Winter); + + bottomBoard.AddCentres([(Nation.Germany, "Ber")]); + + var units = topBoard.AddUnits([(Nation.England, UnitType.Fleet, "Edi")]); + + var englishMove = units.Get("Edi", year: 1902).Move("Cly", year: 1902); + var germanBuild = bottomBoard.Build(Nation.Germany, UnitType.Army, "Ber"); + + // Act + new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate(); + + // Assert + englishMove.Status.Should().Be(OrderStatus.Success); + germanBuild.Status.Should().Be(OrderStatus.Success); + + topBoard.Next().ShouldHaveUnits([(Nation.England, UnitType.Fleet, "Cly", false)]); + bottomBoard.Next().ShouldHaveUnits([(Nation.Germany, UnitType.Army, "Ber", false)]); + } +}