From 8ed862d69fafbfb2338be781efd63ca1ad53f36b Mon Sep 17 00:00:00 2001 From: Oliveriver Date: Mon, 7 Oct 2024 15:14:26 +0100 Subject: [PATCH] Fix a number of bugs relating to retreats --- server/Adjudication/Adjudicator.cs | 2 +- server/Adjudication/Evaluation/Evaluator.cs | 38 +++++++++---------- .../Evaluation/MovementEvaluator.cs | 30 ++++++++------- .../Evaluation/TouchedOrdersFinder.cs | 19 ++++------ server/Adjudication/Execution/Executor.cs | 10 ++--- server/Entities/Board.cs | 2 + server/Entities/Orders/Convoy.cs | 2 +- server/Entities/Orders/Disband.cs | 2 +- server/Entities/Orders/Order.cs | 2 +- server/Entities/Orders/Support.cs | 2 +- server/Entities/Unit.cs | 2 + server/Mappers/EntityMapper.cs | 2 +- server/Tests/Extensions/UnitExtensions.cs | 25 ++++++++++++ server/Tests/MDATC_C.cs | 9 ++++- 14 files changed, 89 insertions(+), 58 deletions(-) diff --git a/server/Adjudication/Adjudicator.cs b/server/Adjudication/Adjudicator.cs index 8fba686..7c7c61f 100644 --- a/server/Adjudication/Adjudicator.cs +++ b/server/Adjudication/Adjudicator.cs @@ -30,7 +30,7 @@ public void Adjudicate() { if (world.Winner != null) { - world.Orders = world.Orders.Where(o => o.Status != OrderStatus.New).ToList(); + world.Orders = world.Orders.Where(o => o.Status is not OrderStatus.New and not OrderStatus.RetreatNew).ToList(); return; } diff --git a/server/Adjudication/Evaluation/Evaluator.cs b/server/Adjudication/Evaluation/Evaluator.cs index 6da1916..e95be05 100644 --- a/server/Adjudication/Evaluation/Evaluator.cs +++ b/server/Adjudication/Evaluation/Evaluator.cs @@ -49,34 +49,32 @@ private List GetActiveOrders() { var newOrders = world.Orders.Where(o => o.Status is OrderStatus.New or OrderStatus.RetreatNew).ToList(); - if (world.HasRetreats) + if (!world.HasRetreats) { - return newOrders; - } - - var idleUnits = world.ActiveBoards - .Where(b => b.Phase != Phase.Winter) - .SelectMany(b => b.Units) - .Where(u => newOrders.All(o => o.Unit != u)); + var idleUnits = world.ActiveBoards + .Where(b => b.Phase != Phase.Winter) + .SelectMany(b => b.Units) + .Where(u => newOrders.All(o => o.Unit != u)); - foreach (var unit in idleUnits) - { - var hold = new Hold + foreach (var unit in idleUnits) { - Status = OrderStatus.New, - Unit = unit, - UnitId = unit.Id, - Location = unit.Location, - }; - world.Orders.Add(hold); - newOrders.Add(hold); + var hold = new Hold + { + Status = OrderStatus.New, + Unit = unit, + UnitId = unit.Id, + Location = unit.Location, + }; + world.Orders.Add(hold); + newOrders.Add(hold); + } } - var activeOrders = touchedOrdersFinder.GetTouchedOrders(newOrders); + var activeOrders = touchedOrdersFinder.GetTouchedOrders(newOrders, world.HasRetreats); foreach (var order in activeOrders) { - if (order.Status != OrderStatus.Invalid) + if (!world.HasRetreats && order.Status != OrderStatus.Invalid) { order.Status = OrderStatus.New; } diff --git a/server/Adjudication/Evaluation/MovementEvaluator.cs b/server/Adjudication/Evaluation/MovementEvaluator.cs index 0aea69e..e0519e8 100644 --- a/server/Adjudication/Evaluation/MovementEvaluator.cs +++ b/server/Adjudication/Evaluation/MovementEvaluator.cs @@ -42,6 +42,22 @@ private void IdentifyRetreats() foreach (var order in activeOrders) { + var unit = order.Unit; + + var existingRetreats = world.Orders + .Where(o => + o.Unit == unit + && o.Status is OrderStatus.RetreatNew + or OrderStatus.RetreatSuccess + or OrderStatus.RetreatFailure + or OrderStatus.RetreatInvalid) + .ToList(); + + foreach (var existingRetreat in existingRetreats) + { + world.Orders.Remove(existingRetreat); + } + var isSuccessfulMove = order is Move && order.Status == OrderStatus.Success; var mustRetreat = !isSuccessfulMove && activeOrders.Any(o => o is Move m @@ -53,20 +69,6 @@ o is Move m continue; } - var unit = order.Unit; - - var existingRetreat = world.Orders.FirstOrDefault(o => - o.Unit == unit - && o.Status is OrderStatus.RetreatNew - or OrderStatus.RetreatSuccess - or OrderStatus.RetreatFailure - or OrderStatus.RetreatInvalid); - - if (existingRetreat != null) - { - world.Orders.Remove(existingRetreat); - } - var canEscape = CanEscape(unit, stationaryOrders, moves.ToList()); if (canEscape) diff --git a/server/Adjudication/Evaluation/TouchedOrdersFinder.cs b/server/Adjudication/Evaluation/TouchedOrdersFinder.cs index 5ed4ab6..e71902a 100644 --- a/server/Adjudication/Evaluation/TouchedOrdersFinder.cs +++ b/server/Adjudication/Evaluation/TouchedOrdersFinder.cs @@ -9,7 +9,7 @@ public class TouchedOrdersFinder(World world, AdjacencyValidator adjacencyValida private readonly AdjacencyValidator adjacencyValidator = adjacencyValidator; - public List GetTouchedOrders(List orders) + public List GetTouchedOrders(List orders, bool hasRetreats) { var depthFirstSearch = new DepthFirstSearch(world, orders, adjacencyValidator); foreach (var order in orders) @@ -17,7 +17,13 @@ public List GetTouchedOrders(List orders) depthFirstSearch.AddTouchedOrders(order); } - return depthFirstSearch.TouchedOrders; + var retreats = depthFirstSearch.TouchedOrders.Where(o => + o.Status is OrderStatus.RetreatNew + or OrderStatus.RetreatSuccess + or OrderStatus.RetreatFailure + or OrderStatus.RetreatInvalid).ToList(); + + return hasRetreats ? retreats : depthFirstSearch.TouchedOrders.Except(retreats).ToList(); } private class DepthFirstSearch(World world, List newOrders, AdjacencyValidator adjacencyValidator) @@ -30,15 +36,6 @@ private class DepthFirstSearch(World world, List newOrders, AdjacencyVali public void AddTouchedOrders(Order order) { - if (order.Status is OrderStatus.RetreatNew - or OrderStatus.RetreatSuccess - or OrderStatus.RetreatFailure - or OrderStatus.RetreatInvalid) - { - TouchedOrders.Remove(order); - return; - } - if (!TouchedOrders.Contains(order)) { TouchedOrders.Add(order); diff --git a/server/Adjudication/Execution/Executor.cs b/server/Adjudication/Execution/Executor.cs index 85c13e8..f7c2ba7 100644 --- a/server/Adjudication/Execution/Executor.cs +++ b/server/Adjudication/Execution/Executor.cs @@ -7,7 +7,7 @@ namespace Adjudication; public class Executor(World world, List regions) { private readonly World world = world; - private readonly List originalRetreatingUnits = world.Boards.SelectMany(b => b.Units).Where(u => u!.MustRetreat).ToList(); + private readonly List originalRetreatingUnits = world.Boards.SelectMany(b => b.Units).Where(u => u.MustRetreat).ToList(); private readonly List regions = regions; private readonly MapComparer mapComparer = new(world.Orders.OfType().ToList()); @@ -74,15 +74,15 @@ private Board AdvanceMajorBoard(Board previousBoard) var year = previousBoard.Year; var phase = previousBoard.Phase.NextPhase(); - var disbands = world.Orders.OfType().Where(d => d.Status == OrderStatus.RetreatSuccess); + var retreats = world.Orders.Where(o => o.Status is OrderStatus.RetreatSuccess or OrderStatus.RetreatFailure or OrderStatus.RetreatInvalid); var holds = world.Orders.Where(o => - (o is not Move || o.Status != OrderStatus.Success) - && !disbands.Any(d => d.Unit == o.Unit) + (o is not Move || o.Status != OrderStatus.Success && o.Status != OrderStatus.RetreatSuccess) + && !retreats.Any(r => r.Unit == o.Unit) && previousBoard.Contains(o.Location) && !originalRetreatingUnits.Contains(o.Unit)); var incomingMoves = world.Orders.OfType().Where(m => - (m.Status == OrderStatus.Success || m.Status == OrderStatus.RetreatSuccess) + m.Status is OrderStatus.Success or OrderStatus.RetreatSuccess && previousBoard.Contains(m.Destination)); var units = new List(); diff --git a/server/Entities/Board.cs b/server/Entities/Board.cs index d1e6dbb..a3b54b1 100644 --- a/server/Entities/Board.cs +++ b/server/Entities/Board.cs @@ -19,4 +19,6 @@ public class Board public bool Contains(Location location) => Timeline == location.Timeline && Year == location.Year && Phase == location.Phase; + + public override string ToString() => $"({Timeline}, {Year}, {Phase})"; } diff --git a/server/Entities/Orders/Convoy.cs b/server/Entities/Orders/Convoy.cs index 180e8ba..2b14e44 100644 --- a/server/Entities/Orders/Convoy.cs +++ b/server/Entities/Orders/Convoy.cs @@ -9,7 +9,7 @@ public class Convoy : Order public Location Destination { get; set; } = null!; [NotMapped] - public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New; + public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New or OrderStatus.RetreatNew; [NotMapped] public override List TouchedLocations => [Location, Midpoint, Destination]; diff --git a/server/Entities/Orders/Disband.cs b/server/Entities/Orders/Disband.cs index 9f975c6..24d1c2e 100644 --- a/server/Entities/Orders/Disband.cs +++ b/server/Entities/Orders/Disband.cs @@ -2,5 +2,5 @@ public class Disband : Order { - public override string ToString() => $"Disband ${Location}: {Status}"; + public override string ToString() => $"Disband {Location}: {Status}"; } diff --git a/server/Entities/Orders/Order.cs b/server/Entities/Orders/Order.cs index 0e2e753..b19e5a2 100644 --- a/server/Entities/Orders/Order.cs +++ b/server/Entities/Orders/Order.cs @@ -19,7 +19,7 @@ public abstract class Order public Location Location { get; set; } = null!; [NotMapped] - public virtual bool NeedsValidation => Status == OrderStatus.New; + public virtual bool NeedsValidation => Status is OrderStatus.New or OrderStatus.RetreatNew; [NotMapped] public virtual List TouchedLocations => [Location]; diff --git a/server/Entities/Orders/Support.cs b/server/Entities/Orders/Support.cs index dbfa40c..d0de944 100644 --- a/server/Entities/Orders/Support.cs +++ b/server/Entities/Orders/Support.cs @@ -9,7 +9,7 @@ public class Support : Order public Location Destination { get; set; } = null!; [NotMapped] - public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New; + public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New or OrderStatus.RetreatNew; [NotMapped] public override List TouchedLocations => [Location, Midpoint, Destination]; diff --git a/server/Entities/Unit.cs b/server/Entities/Unit.cs index 7ba21e8..2400fac 100644 --- a/server/Entities/Unit.cs +++ b/server/Entities/Unit.cs @@ -28,5 +28,7 @@ public class Unit RegionId = Location.RegionId, } }; + + public override string ToString() => $"{Owner} {Type} at {Location}"; } diff --git a/server/Mappers/EntityMapper.cs b/server/Mappers/EntityMapper.cs index 2f4abae..84dc168 100644 --- a/server/Mappers/EntityMapper.cs +++ b/server/Mappers/EntityMapper.cs @@ -8,7 +8,7 @@ public Models.World MapWorld(Entities.World world, Nation? player = null) { var visibleOrders = player == null ? world.Orders - : world.Orders.Where(o => o.Status != OrderStatus.New || o.Unit?.Owner == player); + : world.Orders.Where(o => o.Status is not OrderStatus.New and not OrderStatus.RetreatNew || o.Unit?.Owner == player); var builds = world.Orders.OfType().ToList(); diff --git a/server/Tests/Extensions/UnitExtensions.cs b/server/Tests/Extensions/UnitExtensions.cs index b3421d4..09b1141 100644 --- a/server/Tests/Extensions/UnitExtensions.cs +++ b/server/Tests/Extensions/UnitExtensions.cs @@ -22,6 +22,11 @@ public static Hold Hold(this Unit unit, OrderStatus status = OrderStatus.New) { var world = unit.Board.World; + if (unit.MustRetreat && status == OrderStatus.New) + { + status = OrderStatus.RetreatNew; + } + var hold = new Hold { World = world, @@ -38,6 +43,11 @@ public static Move Move(this Unit unit, string regionId, int timeline = 1, int y { var world = unit.Board.World; + if (unit.MustRetreat && status == OrderStatus.New) + { + status = OrderStatus.RetreatNew; + } + var move = new Move { World = world, @@ -61,6 +71,11 @@ public static Support Support(this Unit unit, Unit supportedUnit, string? region { var world = unit.Board.World; + if (unit.MustRetreat && status == OrderStatus.New) + { + status = OrderStatus.RetreatNew; + } + var support = new Support { World = world, @@ -85,6 +100,11 @@ public static Convoy Convoy(this Unit unit, Unit convoyedUnit, string regionId, { var world = unit.Board.World; + if (unit.MustRetreat && status == OrderStatus.New) + { + status = OrderStatus.RetreatNew; + } + var convoy = new Convoy { World = world, @@ -109,6 +129,11 @@ public static Disband Disband(this Unit unit, OrderStatus status = OrderStatus.N { var world = unit.Board.World; + if (unit.MustRetreat && status == OrderStatus.New) + { + status = OrderStatus.RetreatNew; + } + var disband = new Disband { World = world, diff --git a/server/Tests/MDATC_C.cs b/server/Tests/MDATC_C.cs index 1ff0fa0..a18dab6 100644 --- a/server/Tests/MDATC_C.cs +++ b/server/Tests/MDATC_C.cs @@ -22,11 +22,16 @@ public void MDATC_C_1() var presentBoard = world.AddBoard(phase: Phase.Fall); var pastBoard = world.AddBoard(); - var units = presentBoard.AddUnits([(Nation.England, UnitType.Army, "Lon")]); + List units = + [ + .. pastBoard.AddUnits([(Nation.England, UnitType.Army, "Lon")]), + .. presentBoard.AddUnits([(Nation.England, UnitType.Army, "Lon")]), + ]; + units.Get("Lon").Hold(status: OrderStatus.Success); units.Get("Lon", phase: Phase.Fall).MustRetreat = true; - var order = units.Get("Lon", phase: Phase.Fall).Move("Lon"); + var order = units.Get("Lon", phase: Phase.Fall).Move("Wal"); // Act new Adjudicator(world, false, MapFactory, DefaultWorldFactory).Adjudicate();