-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add order validator and fix formatting check
- Loading branch information
1 parent
4b1e3c0
commit d80edd5
Showing
16 changed files
with
343 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using Entities; | ||
using Enums; | ||
using Utilities; | ||
|
||
namespace Adjudication; | ||
|
||
public class AdjacencyValidator(List<Region> regions) | ||
{ | ||
private readonly List<Region> regions = regions; | ||
|
||
public bool IsValidDirectMove(Unit unit, Location location, Location destination) | ||
{ | ||
if (location.Phase == Phase.Winter || destination.Phase == Phase.Winter) | ||
{ | ||
return false; | ||
} | ||
|
||
var isSameBoard = location.Timeline == destination.Timeline | ||
&& location.Year == destination.Year | ||
&& location.Phase == destination.Phase; | ||
|
||
return isSameBoard | ||
? IsValidIntraBoardMove(unit, location, destination) | ||
: !unit.MustRetreat && IsValidInterBoardMove(unit, location, destination); | ||
} | ||
|
||
public bool IsValidInterBoardMove(Unit unit, Location location, Location destination) | ||
{ | ||
var locationId = location.RegionId; | ||
var destinationId = destination.RegionId; | ||
|
||
if (Constants.UseStrictAdjacencies ? locationId != destinationId : !IsValidIntraBoardMove(unit, location, destination)) | ||
{ | ||
return false; | ||
} | ||
|
||
var yearDistance = location.Year - destination.Year; | ||
var phaseDistance = (int)location.Phase - (int)destination.Phase; | ||
var timeDistance = Math.Abs(2 * yearDistance + phaseDistance); | ||
|
||
var multiverseDistance = Math.Abs(location.Timeline - destination.Timeline); | ||
|
||
return timeDistance <= 1 && multiverseDistance <= 1 && (timeDistance == 0 || multiverseDistance == 0); | ||
} | ||
|
||
public bool IsValidIntraBoardMove(Unit unit, Location location, Location destination) | ||
{ | ||
var locationId = location.RegionId; | ||
var destinationId = destination.RegionId; | ||
|
||
var region = regions.First(r => r.Id == locationId); | ||
|
||
var connection = region.Connections.FirstOrDefault(c => c.Regions.Any(r => r.Id == destinationId)); | ||
if (connection == null) | ||
{ | ||
return false; | ||
} | ||
|
||
var isValidArmyMove = unit.Type == UnitType.Army && connection.Type != ConnectionType.Sea; | ||
var isValidFleetMove = unit.Type == UnitType.Fleet && connection.Type != ConnectionType.Land; | ||
return isValidArmyMove || isValidFleetMove; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using Entities; | ||
using Enums; | ||
|
||
namespace Adjudication; | ||
|
||
public class ConvoyPathValidator(World world, List<Region> regions, AdjacencyValidator adjacencyValidator) | ||
{ | ||
private readonly World world = world; | ||
private readonly List<Region> regions = regions; | ||
|
||
private readonly AdjacencyValidator adjacencyValidator = adjacencyValidator; | ||
|
||
public bool HasPath(Unit unit, Location location, Location destination) | ||
{ | ||
if (unit.Type == UnitType.Fleet) | ||
{ | ||
return false; | ||
} | ||
|
||
var startRegion = regions.First(r => r.Id == location.RegionId); | ||
var endRegion = regions.First(r => r.Id == destination.RegionId); | ||
|
||
var startsOnCoast = startRegion.Type == RegionType.Coast | ||
|| regions.Where(r => r.ParentId == startRegion.Id).Any(r => r.Type == RegionType.Coast); | ||
var endsOnCoast = endRegion.Type == RegionType.Coast | ||
|| regions.Where(r => r.ParentId == endRegion.Id).Any(r => r.Type == RegionType.Coast); | ||
|
||
if (!startsOnCoast || !endsOnCoast) | ||
{ | ||
return false; | ||
} | ||
|
||
var convoysInPath = world.Orders.OfType<Convoy>().Where(c => | ||
c.NeedsValidation | ||
&& c.Midpoint == location | ||
&& c.Destination == destination).ToList(); | ||
|
||
if (convoysInPath.Count == 0) | ||
{ | ||
return false; | ||
} | ||
|
||
var depthFirstSearch = new DepthFirstSearch(convoysInPath, adjacencyValidator); | ||
return depthFirstSearch.HasPath(unit, location, destination); | ||
} | ||
|
||
private class DepthFirstSearch(List<Convoy> convoys, AdjacencyValidator adjacencyValidator) | ||
{ | ||
private readonly List<Convoy> convoys = convoys; | ||
private readonly AdjacencyValidator adjacencyValidator = adjacencyValidator; | ||
|
||
private readonly List<Convoy> visitedConvoys = []; | ||
|
||
public bool HasPath(Unit unit, Location location, Location destination) | ||
{ | ||
if (adjacencyValidator.IsValidDirectMove(unit, location, destination)) | ||
{ | ||
return true; | ||
} | ||
|
||
var convoy = convoys.FirstOrDefault(c => c.Location == location); | ||
if (convoy != null) | ||
{ | ||
visitedConvoys.Add(convoy); | ||
} | ||
|
||
return convoys | ||
.Where(c => !visitedConvoys.Contains(c) && adjacencyValidator.IsValidDirectMove(c.Unit!, c.Location, location)) | ||
.Any(c => HasPath(c.Unit!, c.Location, destination)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
using Entities; | ||
using Enums; | ||
using Factories; | ||
|
||
namespace Adjudication; | ||
|
||
public class Validator(DefaultWorldFactory defaultWorldFactory) | ||
{ | ||
private readonly DefaultWorldFactory defaultWorldFactory = defaultWorldFactory; | ||
|
||
private AdjacencyValidator adjacencyValidator = null!; | ||
private ConvoyPathValidator convoyPathValidator = null!; | ||
private World world = null!; | ||
private List<Region> regions = null!; | ||
|
||
public void Validate(World world, List<Region> regions) | ||
{ | ||
this.world = world; | ||
this.regions = regions; | ||
adjacencyValidator = new(regions); | ||
convoyPathValidator = new(world, regions, adjacencyValidator); | ||
|
||
ValidateMoves(); | ||
ValidateSupports(); | ||
ValidateConvoys(); | ||
ValidateBuilds(); | ||
ValidateDisbands(); | ||
ValidateRetreats(); | ||
} | ||
|
||
private void ValidateMoves() | ||
{ | ||
var moves = world.Orders.Where(o => o.NeedsValidation && !o.Unit!.MustRetreat).OfType<Move>(); | ||
|
||
foreach (var move in moves) | ||
{ | ||
var canDirectMove = adjacencyValidator.IsValidDirectMove(move.Unit!, move.Location, move.Destination); | ||
var canConvoyMove = convoyPathValidator.HasPath(move.Unit!, move.Location, move.Destination); | ||
|
||
move.Status = canDirectMove || canConvoyMove ? OrderStatus.New : OrderStatus.Invalid; | ||
} | ||
} | ||
|
||
private void ValidateSupports() | ||
{ | ||
var supports = world.Orders.Where(o => o.NeedsValidation && !o.Unit!.MustRetreat).OfType<Support>(); | ||
|
||
foreach (var support in supports) | ||
{ | ||
var canSupport = adjacencyValidator.IsValidDirectMove(support.Unit!, support.Location, support.Destination); | ||
var hasMatchingMove = world.Orders | ||
.OfType<Move>() | ||
.Any(m => m.Location == support.Midpoint && m.Destination == support.Destination); | ||
|
||
support.Status = canSupport && hasMatchingMove ? OrderStatus.New : OrderStatus.Invalid; | ||
} | ||
} | ||
|
||
private void ValidateConvoys() | ||
{ | ||
var convoys = world.Orders.Where(o => o.NeedsValidation && !o.Unit!.MustRetreat).OfType<Convoy>(); | ||
|
||
foreach (var convoy in convoys) | ||
{ | ||
// TODO fix for convoying to/from land regions with child coasts | ||
var convoysFromCoast = regions.First(r => r.Id == convoy.Midpoint.RegionId); | ||
var convoysToCoast = regions.First(r => r.Id == convoy.Destination.RegionId); | ||
var hasMatchingMove = world.Orders | ||
.OfType<Move>() | ||
.Any(m => m.Location == convoy.Midpoint && m.Destination == convoy.Destination); | ||
|
||
convoy.Status = hasMatchingMove ? OrderStatus.New : OrderStatus.Invalid; | ||
} | ||
} | ||
|
||
private void ValidateBuilds() | ||
{ | ||
var builds = world.Orders.Where(o => o.NeedsValidation && !o.Unit!.MustRetreat).OfType<Build>(); | ||
var homeCentres = defaultWorldFactory.CreateCentres(); | ||
|
||
foreach (var build in builds) | ||
{ | ||
if (build.Location.Phase != Phase.Winter) | ||
{ | ||
build.Status = OrderStatus.Invalid; | ||
continue; | ||
} | ||
|
||
var board = world.Boards | ||
.FirstOrDefault(b => b.Timeline == build.Location.Timeline && b.Year == build.Location.Year && b.Phase == Phase.Winter); | ||
var region = regions.First(r => r.Id == build.Location.RegionId); | ||
var centre = homeCentres.FirstOrDefault(c => c.Location == build.Location); | ||
var unit = build.Unit!; | ||
|
||
if (board == null || centre == null) | ||
{ | ||
build.Status = OrderStatus.Invalid; | ||
continue; | ||
} | ||
|
||
var isCompatibleRegion = centre.Owner == unit.Owner; | ||
var isCompatibleUnit = unit.Type == UnitType.Army && region.Type != RegionType.Sea | ||
|| unit.Type == UnitType.Fleet && region.Type == RegionType.Coast; | ||
build.Status = isCompatibleRegion && isCompatibleUnit ? OrderStatus.New : OrderStatus.Invalid; | ||
|
||
// NB validation based on available build count to be done as part of adjudication | ||
} | ||
} | ||
|
||
private void ValidateDisbands() | ||
{ | ||
var disbands = world.Orders.Where(o => o.NeedsValidation && !o.Unit!.MustRetreat).OfType<Disband>(); | ||
|
||
foreach (var disband in disbands) | ||
{ | ||
disband.Status = disband.Location.Phase != Phase.Winter ? OrderStatus.New : OrderStatus.Invalid; | ||
|
||
// NB validation based on available build count to be done as part of adjudication | ||
} | ||
} | ||
|
||
private void ValidateRetreats() | ||
{ | ||
var retreats = world.Orders.Where(o => o.NeedsValidation && o.Unit!.MustRetreat); | ||
|
||
foreach (var retreat in retreats) | ||
{ | ||
retreat.Status = retreat switch | ||
{ | ||
Move move => adjacencyValidator.IsValidDirectMove(move.Unit!, move.Location, move.Destination) | ||
? OrderStatus.New | ||
: OrderStatus.Invalid, | ||
Disband => OrderStatus.New, | ||
_ => OrderStatus.Invalid, | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
namespace Entities; | ||
using Enums; | ||
using System.ComponentModel.DataAnnotations.Schema; | ||
|
||
namespace Entities; | ||
|
||
public class Convoy : Order | ||
{ | ||
public Location Midpoint { get; set; } = null!; | ||
public Location Destination { get; set; } = null!; | ||
|
||
[NotMapped] | ||
public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
namespace Entities; | ||
using Enums; | ||
using System.ComponentModel.DataAnnotations.Schema; | ||
|
||
namespace Entities; | ||
|
||
public class Support : Order | ||
{ | ||
public Location Midpoint { get; set; } = null!; | ||
public Location Destination { get; set; } = null!; | ||
|
||
[NotMapped] | ||
public override bool NeedsValidation => Status is OrderStatus.Invalid or OrderStatus.New; | ||
} |
Oops, something went wrong.