Compare commits

..

4 Commits

11 changed files with 299 additions and 5 deletions

27
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/MultiversalDiplomacy/bin/Debug/net6.0/MultiversalDiplomacy.dll",
"args": ["repl"],
"cwd": "${workspaceFolder}/MultiversalDiplomacy",
"console": "internalConsole",
"stopAtEntry": true
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
],
"logging": {
"engineLogging": true
}
}

41
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/MultiversalDiplomacy/MultiversalDiplomacy.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/MultiversalDiplomacy/MultiversalDiplomacy.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/MultiversalDiplomacy/MultiversalDiplomacy.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -8,6 +8,18 @@ namespace MultiversalDiplomacy.Adjudicate;
/// </summary> /// </summary>
public interface IPhaseAdjudicator public interface IPhaseAdjudicator
{ {
/// <summary>
/// Given a list of order sets, determine which entries are comprehensible as orders.
/// An order set entry may comprehensible as an order but not valid for the current
/// phase; these orders will be rejected by <see cref="ValidateOrders"/>.
/// </summary>
/// <param name="world">The global game state.</param>
/// <param name="orderSets">The order sets to adjudicate.</param>
/// <returns>
/// A list of <see cref="Order"/> objects representing the orders parsed.
/// </returns>
public List<Order> ParseOrderSets(World world, List<OrderSet> orderSets);
/// <summary> /// <summary>
/// Given a list of orders, determine which orders are valid for this adjudicator and /// Given a list of orders, determine which orders are valid for this adjudicator and
/// which should be rejected before adjudication. Adjudication should be performed on /// which should be rejected before adjudication. Adjudication should be performed on

View File

@ -1,3 +1,5 @@
using System.Linq;
using MultiversalDiplomacy.Adjudicate.Decision; using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Adjudicate.Logging; using MultiversalDiplomacy.Adjudicate.Logging;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
@ -19,6 +21,36 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
this.logger = logger; this.logger = logger;
} }
public List<Order> ParseOrderSets(World world, List<OrderSet> orderSets)
{
foreach (OrderSet orderSet in orderSets)
{
string[] lines = orderSet.Text.Split('\n');
string powerLine = lines[0];
// TODO verify
string powerName = powerLine.Substring("orders ".Length);
foreach (string line in lines.Skip(1))
{
// Individual order components do not have spaces in them
string[] tokens = line.Split(' ');
int i = 0;
// Check for a unit type
string[] unitTypes = new string[] { "a", "f"};
if (unitTypes.Contains(tokens[i].ToLowerInvariant()))
{
// yay
i++;
}
//
}
}
throw new NotImplementedException();
}
public List<OrderValidation> ValidateOrders(World world, List<Order> orders) public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
{ {
// The basic workflow of this function will be to look for invalid orders, remove these // The basic workflow of this function will be to look for invalid orders, remove these

View File

@ -1,4 +1,6 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacy; namespace MultiversalDiplomacy;
@ -21,9 +23,28 @@ public static class GameController
public static World AdjudicateOrders(World world) public static World AdjudicateOrders(World world)
{ {
// TODO: Parse the order sets into orders // Determine which phase the game is in, which determines how orders should be interpreted and adjudicated.
// TODO: Execute the correct adjudicator for the current world state PhaseType phaseType = world.GetNextPhaseType();
// TODO: Update the world
return world; IPhaseAdjudicator adjudicator = phaseType switch {
PhaseType.Movement => MovementPhaseAdjudicator.Instance,
PhaseType.Retreat => throw new NotImplementedException(),
PhaseType.Build => throw new NotImplementedException(),
PhaseType.Sustain => throw new NotImplementedException(),
_ => throw new InvalidOperationException(phaseType.ToString()),
};
// Parse the order sets into actual orders.
List<Order> parsedOrders = adjudicator.ParseOrderSets(world, world.OrderSets.ToList());
// Validate the orders.
var orderValidations = adjudicator.ValidateOrders(world, parsedOrders);
// Adjudicate the orders.
var validOrders = orderValidations.Where(v => v.Valid).Select(v => v.Order).ToList();
var results = adjudicator.AdjudicateOrders(world, validOrders);
// Update the world.
return adjudicator.UpdateWorld(world, results);
} }
} }

View File

@ -0,0 +1,9 @@
namespace MultiversalDiplomacy.Model;
public enum PhaseType
{
Movement = 1,
Retreat = 2,
Build = 3,
Sustain = 4,
}

View File

@ -309,6 +309,17 @@ public class World
return foundUnit; return foundUnit;
} }
public PhaseType GetNextPhaseType()
{
// TODO: Figure how to order build and sustain phases in a staggered multiverse
if (RetreatingUnits.Any())
{
return PhaseType.Retreat;
}
return PhaseType.Movement;
}
/// <summary> /// <summary>
/// The standard Diplomacy provinces. /// The standard Diplomacy provinces.
/// </summary> /// </summary>

View File

@ -30,8 +30,31 @@ public class GameScriptHandler : IScriptHandler
case "help": case "help":
case "?": case "?":
Console.WriteLine("commands:"); Console.WriteLine("commands:");
Console.WriteLine(" adjudicate: adjudicate the current orders");
Console.WriteLine(" assert: assert about the state of the game");
Console.WriteLine(" list: list things in a game category"); Console.WriteLine(" list: list things in a game category");
Console.WriteLine(" orders: submit order sets"); Console.WriteLine(" orders: submit order sets");
Console.WriteLine(" status: overview of the state of the game");
break;
case "adjudicate":
World = GameController.AdjudicateOrders(World);
break;
case "assert" when args.Length == 1:
Console.WriteLine("usage:");
Console.WriteLine(" assert timeline [timeline]@[turn]: timeline exists");
Console.WriteLine(" assert unit [unit spec]: unit exists");
break;
case "assert" when args[1] == "timeline":
// TODO: raise an error if the timeline doesn't exist
Console.WriteLine("WIP");
break;
case "assert" when args[1] == "unit":
// TODO: raise an error if the unit doesn't exist
Console.WriteLine("WIP");
break; break;
case "list" when args.Length == 1: case "list" when args.Length == 1:
@ -70,6 +93,17 @@ public class GameScriptHandler : IScriptHandler
Console.WriteLine("Unrecognized power"); Console.WriteLine("Unrecognized power");
break; break;
case "status":
foreach (Season season in World.Seasons)
{
Console.WriteLine($"{season}");
foreach (Unit unit in World.Units.Where(u => u.Season == season))
{
Console.WriteLine($" {unit}");
}
}
break;
default: default:
Console.WriteLine("Unrecognized command"); Console.WriteLine("Unrecognized command");
break; break;

View File

@ -22,8 +22,102 @@ When a unit changes the outcome of a battle in the past, only the timeline of th
Since there are many ways to create new timelines, the game would rapidly expand beyond all comprehension if this were not counterbalanced in some way. This happens during the _sustain phase_, which occurs after the fall movement and retreat phases and before the winter buid/disband phase. Since there are many ways to create new timelines, the game would rapidly expand beyond all comprehension if this were not counterbalanced in some way. This happens during the _sustain phase_, which occurs after the fall movement and retreat phases and before the winter buid/disband phase.
(TODO) WIP: the sustain phase has not been implemented yet
### Victory conditions ### Victory conditions
The Great Powers of Europe can only wage multiversal wars because they are lead by extradimensional beings masquerading as human politicians. When a country is eliminated in one timeline, its extradimensional leader is executed, killing them in all timelines. The Great Powers of Europe can only wage multiversal wars because they are lead by extradimensional beings masquerading as human politicians. When a country is eliminated in one timeline, its extradimensional leader is executed, killing them in all timelines.
### Unit designations
In _Diplomacy_, orders refer to provinces, such as "A Mun-Tyr". In _5D Diplomacy with Multiversal Time Travel_, this is insufficient to unambiguously identify a province, since the province exists in multiple timelines across multiple turns. The convention for identifying a multiversal location is `timeline:province:turn`, where `timeline` is the timeline's identifier and `turn` is the turn's identifier. Thus, an army in Munich in timeline "bravo" in Spring 1902 is referenced as "A b:Mun:S02".
(Why this order? Short representations for timelines and turns can be confused for each other, especially for timelines designated with `foxtrot` or `sierra` and turns in Fall or Spring. _5D Diplomacy with Multiversal Time Travel_ is already complicated enough, so the timeline and turn are put on either side of the province.)
WIP: parsing of turn designations has not been implemented yet
### Open convoys
The standard _Diplomacy_ rules require that a convoy order include the convoyed unit's origin and destination. This is hard to coordinate once there are multiple turns and timelines involved. _5D Diplomacy with Multiversal Time Travel_ thus introduces the concept of an _open convoy_, a nonspecific convoy order that can become part of a convoy later.
WIP: open convoys have not been implemented yet
## DATC compliance
WIP
## 5dp script
Order notation is case-insensitive.
### Order element grammar
A unit type is specified with a single letter.
```
<unit-spec> = "A" / "F"
```
A timeline is specified with a primary designation initial, plus a secondary designation if there are enough timelines.
```
<timeline-spec> = <tl-primary> <tl-secondary>
<tl-primary> = "a" / "b" / ...
<tl-secondary> = "" / "1" / "2" / ...
```
A province is specified with a known three-letter abbreviation. A named location in a province may optionally be specfied with its abbreviation. A named location is not necessary for the _form_ of a province to be valid, though an order may be invalid if the omission creates an ambiguity.
```
<province-spec> = <province> ["/" <location>]
<province> = "MUN" / "TYR" / ...
<location> = "nc" / "sc" / ...
```
A turn is specified with the season's initial and the short form of the year. Winter is numbered in the year of the previous Fall.
```
<turn-spec> = <season> <year>
<season> = "S" / "F" / "W"
<year> = "01" / "02" / ...
```
A multiversal location is a timeline, a province, and a turn separated by a colon.
```
<multiverse-spec> = <timeline-spec> ":" <province-spec> ":" <turn-spec>
```
Thus, `b1:IRI:F02` represents the Irish Sea, in Fall 1902, in timeline bravo-prime.
### Order formats
Note that DATC 4.C makes unit designations superfluous outside some build order cases. Thus, the `<unit-spec>` is considered optional in the orders below.
Hold orders require the unit and an indication of a hold order.
```
<hold-order> = [<unit-spec> " "] <multiverse-spec> " " <hold-token>
<hold-token> = "hold" / "holds"
```
Move orders require the unit, target, and an indication of movement instead of support.
```
<move-order> = [<unit-spec> " "] <multiverse-spec> <move-token> <multiverse-spec>
<move-token> = "-" / " to "
```
Support-hold orders require the unit, target, and an indication of support instead of movement.
```
<support-hold-order> = [<unit-spec> " "] <multiverse-spec> <support-token> <multiverse-spec>
<support-token> = " S " / " support " / " supports "
```
Support-move orders require the unit, target, and the support and move indicators.
```
<support-move-order> = [<unit-spec> " "] <multiverse-spec> <support-token> <move-order>
```
Convoy orders WIP.
Retreat orders WIP.
Build orders WIP.
Disband orders WIP.
Sustain orders WIP.

12
script.txt Normal file
View File

@ -0,0 +1,12 @@
new custom
unit Germany Army Berlin
unit Austria Army Tyrolia
orders Germany
BER - TYR
orders Austria
TYR hold
list ordersets
adjudicate

1
test.sh Normal file
View File

@ -0,0 +1 @@
dotnet test -l "console;verbosity=normal" MultiversalDiplomacyTests/ $@