Update timeline designator usage

Timelines are now identified by strings and come first in timeline-turn tuples.
This commit is contained in:
Tim Van Baak 2024-08-12 09:28:56 -07:00
parent 780ae8b948
commit 421e84b559
8 changed files with 166 additions and 175 deletions

View File

@ -236,17 +236,13 @@
<a name="2.B"><h3>2.B. ORDER NOTATION</h3></a>
<p>The order notation in this document is as in DATC, with the following additions for multiversal time travel.</p>
<ul>
<li>A season within a particular timeline is designated in the format X:Y, where X is the turn (starting from 0 and advancing with each movement phase) and Y is the timeline number (starting from 0 and advancing with each timeline fork).</li>
<li>Adjudication is implied to be done between successive seasons. For example, if orders are listed for 0:0 and then for 1:0, it is implied that the orders for 0:0 were adjudicated.</li>
<li>Units are designated by unit type, province, and season, e.g. "A Munich 1:0". A destination for a move order or support-to-move order is designated by province and season, e.g. "Munich 1:0".
<li>Timelines are designated by letters, e.g. "a", "b". Turns are designated by numbers, e.g. 0, 1, 2. A board in a timeline X at turn N is designated XN. A location LOC on that board is designated X-LOC@N.</li>
<li>In examples that cover multiple turns, orders are given in sets. Each order set is adjudicated before moving on to the next set.</li>
<li>Units are fully designated by unit type and multiversal location, e.g. "A b-Munich@3". Destinations for move orders or support-to-move orders are fully designated by multiversal location, e.g. <code>a-Munich@1</code>. Where orders are not fully designated, the full designations are implied according to these rules:</li>
<ul>
<li>If season of the ordered unit is not specified, the season is the season to which the orders are being given.</li>
<li>If the season of a unit supported to hold is not specified, the season is the same season as the supporting unit.</li>
<li>If the season of the destination of a move order or the season of the destination of a supported move order is not specified, the season is the season of the moving unit.</li>
<li>For example:
<pre>Germany 2:0
A Munich supports A Munich 1:1 - Tyrolia</pre>
The order here is for Army Munich in 2:0. The move being supported is for Army Munich in 1:1 to move to Tyrolia in 1:1.</li>
<li>If only the timeline of a location is specified, the turn is the latest turn in that timeline. E.g. if timeline "a" is at turn 2, <code>a-Munich</code> is interpreted as <code>a-Munich@2</code>.</li>
<li>If the timeline or turn are unspecified for the target of a move or support-hold order, the timeline and turn are those of the ordered unit. E.g. if timeline "b" is at turn 1, <code>A b-Tyrolia - Munich</code> is interpreted as <code>b-Tyrolia@1 - b-Munich@1</code>.</li>
<li>If only the province is specified for the target of a support-move order, the timeline and turn are those of the supported unit. E.g. if timeline "a" is at turn 2 and "b" at turn 1, <code>A a-Munich supports A b-Tyrolia - Munich</code> is interpreted as <code>A a-Munich@2 supports A b-Tyrolia@1 - b-Munich@1</code>.</li>
</ul>
</ul>
@ -258,13 +254,15 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<summary><h4><a href="#3.A.1">3.A.1</a>. TEST CASE, MOVE INTO OWN PAST FORKS TIMELINE</h4></summary>
<p>A unit that moves into its own immediate past causes the timeline to fork.</p>
<pre>
Germany 0:0
A Munich hold
Germany:
A a-Munich hold
Germany 1:0
A Munich - Tyrolia 0:0
---
Germany:
A a-Munich - a-Tyrolia@0
</pre>
<p>A Munich 1:0 moves to Tyrolia 0:0. The main timeline advances to 2:0 with an empty board. A forked timeline advances to 1:1 with armies in Munich and Tyrolia.</p>
<p>A a-Munich@1 moves to a-Tyrolia@0. The main timeline advances to a2 with an empty board. A forked timeline advances to b1 with armies in Munich and Tyrolia.</p>
<div class="figures">
<canvas id="canvas-3-A-1-before" width="0" height="0"></canvas>
<script>
@ -311,19 +309,21 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<summary><h4><a href="#3.A.2">3.A.2</a>. TEST CASE, SUPPORT TO REPELLED PAST MOVE FORKS TIMELINE</h4></summary>
<p>A unit that supports a move that previously failed in the past, such that it now succeeds, causes the timeline to fork.</p>
<pre>
Austria 0:0
Austria:
A Tyrolia hold
Germany 0:0
Germany:
A Munich - Tyrolia
Austria 1:0
---
Austria:
A Tyrolia hold
Germany 1:0
A Munich supports A Munich 0:0 - Tyrolia 0:0
Germany:
A Munich supports A a-Munich@0 - Tyrolia
</pre>
<p>With the support from A Munich 1:0, A Munich 0:0 dislodges A Tyrolia 0:0. A forked timeline advances to 1:1 where A Tyrolia 0:0 has been dislodged. The main timeline advances to 2:0 where A Munich and A Tyrolia are in their initial positions.</p>
<p>With the support from A a-Munich@1, A a-Munich@0 dislodges A a-Tyrolia@0. A forked timeline advances to b1 where A Tyrolia has been dislodged. The main timeline advances to a2 where A Munich and A Tyrolia are in their initial positions.</p>
<div class="figures">
<canvas id="canvas-3-A-2-before" width="0" height="0"></canvas>
<script>
@ -379,19 +379,21 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<summary><h4><a href="#3.A.3">3.A.3</a>. TEST CASE, FAILED MOVE DOES NOT FORK TIMELINE</h4></summary>
<p>A unit that attempts to move into the past, but is repelled, does not cause the timeline to fork.</p>
<pre>
Austria 0:0
Austria:
A Tyrolia hold
Germany 0:0
Germany:
A Munich hold
Austria 1:0
---
Austria:
A Tyrolia hold
Germany 1:0
A Munich - Tyrolia 0:0
Germany:
A Munich - a-Tyrolia@0
</pre>
<p>The move by A Munich 1:0 fails. The main timeline advances to 2:0 with both armies in their initial positions. No alternate timeline is created.</p>
<p>The move by A a-Munich@1 fails. The main timeline advances to a2 with both armies in their initial positions. No alternate timeline is created.</p>
<div class="figures">
<canvas id="canvas-3-A-3-before" width="0" height="0"></canvas>
<script>
@ -441,15 +443,17 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<summary><h4><a href="#3.A.4">3.A.4</a>. TEST CASE, SUPERFLUOUS SUPPORT DOES NOT FORK TIMELINE</h4></summary>
<p>A unit that supports a move that succeeded in the past and still succeeds after the additional future support does not cause the timeline to fork.</p>
<pre>
Germany 0:0
Germany:
A Munich - Tyrolia
A Bohemia hold
Germany 1:0
---
Germany:
A Tyrolia hold
A Bohemia supports A Munich 0:0 - Tyrolia
A Bohemia supports A a-Munich@0 - Tyrolia
</pre>
<p>Both units in 1:0 continue to 2:0. No alternate timeline is created.</p>
<p>Both units in a1 continue to a2. No alternate timeline is created.</p>
<div class="figures">
<canvas id="canvas-3-A-4-before" width="0" height="0"></canvas>
<script>
@ -501,15 +505,15 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<summary><h4><a href="#3.A.5">3.A.5</a>. TEST CASE, CROSS-TIMELINE SUPPORT DOES NOT FORK HEAD</h4></summary>
<p>In this test case, a unit elsewhere on the map moves into the past to cause a timeline fork. Once there are two parallel timelines, a support from one to the head of the other should not cause any forking, since timeline forks only occur when the past changes, not the present.</p>
<pre>
Austria
A Tyrolia 2:0 hold
A Tyrolia 1:1 hold
Austria:
A a-Tyrolia hold
A b-Tyrolia hold
Germany
A Munich 2:0 - Tyrolia
A Munich 1:1 supports A Munich 2:0 - Tyrolia
A a-Munich - Tyrolia
A b-Munich supports A a-Munich - Tyrolia
</pre>
<p>A Munich 2:0 dislodges A Tyrolia 2:0. No alternate timeline is created.</p>
<p>A a-Munich dislodges A a-Tyrolia. No alternate timeline is created.</p>
<div class="figures">
<canvas id="canvas-3-A-5-before" width="0" height="0"></canvas>
<script>
@ -567,19 +571,11 @@ The order here is for Army Munich in 2:0. The move being supported is for Army M
<p>Following <a href="#3.A.5">3.A.5</a>, a cross-timeline support that previously succeeded is cut.</p>
<pre>
Germany
A Munich 2:0 - Tyrolia
A Munich 1:1 supports A Munich 2:0 - Tyrolia
A a-Tyrolia holds
A b-Munich holds
Austria
A Tyrolia 2:0 holds
A Tyrolia 1:1 holds
Germany
A Tyrolia 3:0 holds
A Munich 2:1 holds
Austria
A Tyrolia 2:1 - Munich 1:1
A b-Tyrolia@2 - b-Munich@1
</pre>
<p>Cutting the support does not change the past or cause a timeline fork.</p>
<div class="figures">

View File

@ -27,12 +27,12 @@ public class Season
/// <summary>
/// The timeline to which this season belongs.
/// </summary>
public int Timeline { get; }
public string Timeline { get; }
/// <summary>
/// The season's spatial location as a turn-timeline tuple.
/// The season's spatial location as a timeline-turn tuple.
/// </summary>
public (int Turn, int Timeline) Coord => (this.Turn, this.Timeline);
public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn);
/// <summary>
/// The shared timeline number generator.
@ -45,18 +45,15 @@ public class Season
public IEnumerable<Season> Futures => this.FutureList;
private List<Season> FutureList { get; }
private Season(Season? past, int turn, int timeline, TimelineFactory factory)
private Season(Season? past, int turn, string timeline, TimelineFactory factory)
{
this.Past = past;
this.Turn = turn;
this.Timeline = timeline;
this.Timelines = factory;
this.FutureList = new();
this.FutureList = [];
if (past != null)
{
past.FutureList.Add(this);
}
past?.FutureList.Add(this);
}
public override string ToString()
@ -73,7 +70,7 @@ public class Season
return new Season(
past: null,
turn: FIRST_TURN,
timeline: TimelineFactory.StringToInt(factory.NextTimeline()),
timeline: factory.NextTimeline(),
factory: factory);
}
@ -81,13 +78,13 @@ public class Season
/// Create a season immediately after this one in the same timeline.
/// </summary>
public Season MakeNext()
=> new Season(this, this.Turn + 1, this.Timeline, this.Timelines);
=> new(this, Turn + 1, Timeline, Timelines);
/// <summary>
/// Create a season immediately after this one in a new timeline.
/// </summary>
public Season MakeFork()
=> new Season(this, this.Turn + 1, TimelineFactory.StringToInt(Timelines.NextTimeline()), this.Timelines);
=> new(this, Turn + 1, Timelines.NextTimeline(), Timelines);
/// <summary>
/// Returns the first season in this season's timeline. The first season is the
@ -127,7 +124,7 @@ public class Season
/// </summary>
public IEnumerable<Season> GetAdjacentSeasons()
{
List<Season> adjacents = new();
List<Season> adjacents = [];
// The immediate past and all immediate futures are adjacent.
if (this.Past != null) adjacents.Add(this.Past);

View File

@ -207,29 +207,27 @@ public class World
/// <summary>
/// Get a season by coordinate. Throws if the season is not found.
/// </summary>
public Season GetSeason(int turn, int timeline)
public Season GetSeason(string timeline, int turn)
{
Season? foundSeason = this.Seasons.SingleOrDefault(
s => s!.Turn == turn && s.Timeline == timeline,
null);
if (foundSeason == null) throw new KeyNotFoundException(
$"Season {turn}:{timeline} not found");
null)
?? throw new KeyNotFoundException($"Season {timeline}@{turn} not found");
return foundSeason;
}
/// <summary>
/// Returns a unit in a province. Throws if there are duplicate units.
/// </summary>
public Unit GetUnitAt(string provinceName, (int turn, int timeline)? seasonCoord = null)
public Unit GetUnitAt(string provinceName, (string timeline, int turn)? seasonCoord = null)
{
Province province = Map.GetProvince(provinceName);
seasonCoord ??= (this.RootSeason.Turn, this.RootSeason.Timeline);
Season season = GetSeason(seasonCoord.Value.turn, seasonCoord.Value.timeline);
seasonCoord ??= (this.RootSeason.Timeline, this.RootSeason.Turn);
Season season = GetSeason(seasonCoord.Value.timeline, seasonCoord.Value.turn);
Unit? foundUnit = this.Units.SingleOrDefault(
u => u!.Province == province && u.Season == season,
null);
if (foundUnit == null) throw new KeyNotFoundException(
$"Unit at {province} at {season} not found");
null)
?? throw new KeyNotFoundException($"Unit at {province} at {season} not found");
return foundUnit;
}
}

View File

@ -14,12 +14,12 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// Hold to move into the future, then move back into the past.
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s0)
["Germany"]
.Army("Mun").Holds().GetReference(out var mun0)
.Execute()
[(1, 0)]
[("a", 1)]
.GetReference(out Season s1)
["Germany"]
.Army("Mun").MovesTo("Tyr", season: s0).GetReference(out var mun1);
@ -41,15 +41,15 @@ public class TimeTravelTest
Is.EqualTo(1),
"Failed to fork timeline when unit moved in");
// Confirm that there is a unit in Tyr 1:1 originating from Mun 1:0
Season fork = world.GetSeason(1, 1);
// Confirm that there is a unit in Tyr b1 originating from Mun a1
Season fork = world.GetSeason("b", 1);
Unit originalUnit = world.GetUnitAt("Mun", s0.Coord);
Unit aMun0 = world.GetUnitAt("Mun", s1.Coord);
Unit aTyr = world.GetUnitAt("Tyr", fork.Coord);
Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit));
Assert.That(aTyr.Past?.Past, Is.EqualTo(mun0.Order.Unit));
// Confirm that there is a unit in Mun 1:1 originating from Mun 0:0
// Confirm that there is a unit in Mun b1 originating from Mun a0
Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
}
@ -60,7 +60,7 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// Fail to dislodge on the first turn, then support the move so it succeeds.
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s0)
["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var mun0)
@ -75,7 +75,7 @@ public class TimeTravelTest
Assert.That(tyr0, Is.NotDislodged);
setup.UpdateWorld();
setup[(1, 0)]
setup[("a", 1)]
["Germany"]
.Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1)
["Austria"]
@ -91,16 +91,16 @@ public class TimeTravelTest
// Confirm that an alternate future is created.
World world = setup.UpdateWorld();
Season fork = world.GetSeason(1, 1);
Season fork = world.GetSeason("b", 1);
Unit tyr1 = world.GetUnitAt("Tyr", fork.Coord);
Assert.That(
tyr1.Past,
Is.EqualTo(mun0.Order.Unit),
"Expected A Mun 0:0 to advance to Tyr 1:1");
"Expected A Mun a0 to advance to Tyr b1");
Assert.That(
world.RetreatingUnits.Count,
Is.EqualTo(1),
"Expected A Tyr 0:0 to be in retreat");
"Expected A Tyr a0 to be in retreat");
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit));
}
@ -110,14 +110,14 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// Hold to create a future, then attempt to attack in the past.
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s0)
["Germany"]
.Army("Mun").Holds()
["Austria"]
.Army("Tyr").Holds().GetReference(out var tyr0)
.Execute()
[(1, 0)]
[("a", 1)]
.GetReference(out Season s1)
["Germany"]
.Army("Mun").MovesTo("Tyr", season: s0).GetReference(out var mun1)
@ -128,7 +128,7 @@ public class TimeTravelTest
Assert.That(mun1, Is.Valid);
setup.AdjudicateOrders();
Assert.That(mun1, Is.Repelled);
// The order to Tyr 0:0 should have been pulled into the adjudication set, so the reference
// The order to Tyr a0 should have been pulled into the adjudication set, so the reference
// should not throw when accessing it.
Assert.That(tyr0, Is.NotDislodged);
@ -140,7 +140,7 @@ public class TimeTravelTest
Is.EqualTo(1),
"A failed move incorrectly forked the timeline");
Assert.That(s1.Futures.Count(), Is.EqualTo(1));
Season s2 = world.GetSeason(s1.Turn + 1, s1.Timeline);
Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1);
Assert.That(s2.Futures.Count(), Is.EqualTo(0));
}
@ -150,7 +150,7 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// Move, then support the past move even though it succeeded already.
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s0)
["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var mun0)
@ -162,7 +162,7 @@ public class TimeTravelTest
Assert.That(mun0, Is.Victorious);
setup.UpdateWorld();
setup[(1, 0)]
setup[("a", 1)]
.GetReference(out Season s1)
["Germany"]
.Army("Tyr").Holds()
@ -182,7 +182,7 @@ public class TimeTravelTest
Is.EqualTo(1),
"A superfluous support incorrectly forked the timeline");
Assert.That(s1.Futures.Count(), Is.EqualTo(1));
Season s2 = world.GetSeason(s1.Turn + 1, s1.Timeline);
Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1);
Assert.That(s2.Futures.Count(), Is.EqualTo(0));
}
@ -192,51 +192,51 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// London creates two timelines by moving into the past.
setup[(0, 0)]
.GetReference(out var s0_0)
setup[("a", 0)]
.GetReference(out var a0)
["England"].Army("Lon").Holds()
["Austria"].Army("Tyr").Holds()
["Germany"].Army("Mun").Holds()
.Execute()
[(1, 0)]
["England"].Army("Lon").MovesTo("Yor", s0_0)
[("a", 1)]
["England"].Army("Lon").MovesTo("Yor", a0)
.Execute()
// Head seasons: 2:0 1:1
// Head seasons: a2 b1
// Both contain copies of the armies in Mun and Tyr.
// Now Germany dislodges Austria in Tyr by supporting the move across timelines.
[(2, 0)]
.GetReference(out var s2_0)
["Germany"].Army("Mun").MovesTo("Tyr").GetReference(out var mun2_0)
["Austria"].Army("Tyr").Holds().GetReference(out var tyr2_0)
[(1, 1)]
.GetReference(out var s1_1)
["Germany"].Army("Mun").Supports.Army("Mun", s2_0).MoveTo("Tyr").GetReference(out var mun1_1)
[("a", 2)]
.GetReference(out var a2)
["Germany"].Army("Mun").MovesTo("Tyr").GetReference(out var mun_a2)
["Austria"].Army("Tyr").Holds().GetReference(out var tyr_a2)
[("b", 1)]
.GetReference(out var b1)
["Germany"].Army("Mun").Supports.Army("Mun", a2).MoveTo("Tyr").GetReference(out var mun_b1)
["Austria"].Army("Tyr").Holds();
// The attack against Tyr 2:0 succeeds.
// The attack against Tyr a2 succeeds.
setup.ValidateOrders();
Assert.That(mun2_0, Is.Valid);
Assert.That(tyr2_0, Is.Valid);
Assert.That(mun1_1, Is.Valid);
Assert.That(mun_a2, Is.Valid);
Assert.That(tyr_a2, Is.Valid);
Assert.That(mun_b1, Is.Valid);
setup.AdjudicateOrders();
Assert.That(mun2_0, Is.Victorious);
Assert.That(tyr2_0, Is.Dislodged);
Assert.That(mun1_1, Is.NotCut);
Assert.That(mun_a2, Is.Victorious);
Assert.That(tyr_a2, Is.Dislodged);
Assert.That(mun_b1, Is.NotCut);
// Since both seasons were at the head of their timelines, there should be no forking.
World world = setup.UpdateWorld();
Assert.That(
s2_0.Futures.Count(),
a2.Futures.Count(),
Is.EqualTo(1),
"A cross-timeline support incorrectly forked the head of the timeline");
Assert.That(
s1_1.Futures.Count(),
b1.Futures.Count(),
Is.EqualTo(1),
"A cross-timeline support incorrectly forked the head of the timeline");
Season s3_0 = world.GetSeason(s2_0.Turn + 1, s2_0.Timeline);
Assert.That(s3_0.Futures.Count(), Is.EqualTo(0));
Season s2_1 = world.GetSeason(s1_1.Turn + 1, s1_1.Timeline);
Assert.That(s2_1.Futures.Count(), Is.EqualTo(0));
Season a3 = world.GetSeason(a2.Timeline, a2.Turn + 1);
Assert.That(a3.Futures.Count(), Is.EqualTo(0));
Season b2 = world.GetSeason(b1.Timeline, b1.Turn + 1);
Assert.That(b2.Futures.Count(), Is.EqualTo(0));
}
[Test]
@ -245,63 +245,63 @@ public class TimeTravelTest
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
// As above, only now London creates three timelines.
setup[(0, 0)]
.GetReference(out var s0_0)
setup[("a", 0)]
.GetReference(out var a0)
["England"].Army("Lon").Holds()
["Austria"].Army("Boh").Holds()
["Germany"].Army("Mun").Holds()
.Execute()
[(1, 0)]
["England"].Army("Lon").MovesTo("Yor", s0_0)
[("a", 1)]
["England"].Army("Lon").MovesTo("Yor", a0)
.Execute()
// Head seasons: 2:0 1:1
[(2, 0)]
[(1, 1)]
["England"].Army("Yor").MovesTo("Edi", s0_0)
// Head seasons: a2, b1
[("a", 2)]
[("b", 1)]
["England"].Army("Yor").MovesTo("Edi", a0)
.Execute()
// Head seasons: 3:0 2:1 1:2
// Head seasons: a3, b2, c1
// All three of these contain copies of the armies in Mun and Tyr.
// As above, Germany dislodges Austria in Tyr 3:0 by supporting the move from 2:1.
[(3, 0)]
.GetReference(out var s3_0)
// As above, Germany dislodges Austria in Tyr a3 by supporting the move from b2.
[("a", 3)]
.GetReference(out var a3)
["Germany"].Army("Mun").MovesTo("Tyr")
["Austria"].Army("Tyr").Holds()
[(2, 1)]
.GetReference(out Season s2_1)
["Germany"].Army("Mun").Supports.Army("Mun", s3_0).MoveTo("Tyr").GetReference(out var mun2_1)
[("b", 2)]
.GetReference(out Season b2)
["Germany"].Army("Mun").Supports.Army("Mun", a3).MoveTo("Tyr").GetReference(out var mun_b2)
["Austria"].Army("Tyr").Holds()
[(1, 2)]
[("c", 1)]
["Germany"].Army("Mun").Holds()
["Austria"].Army("Tyr").Holds()
.Execute()
// Head seasons: 4:0 3:1 2:2
// Now Austria cuts the support in 2:1 by attacking from 2:2.
[(4, 0)]
// Head seasons: a4, b3, c2
// Now Austria cuts the support in b2 by attacking from c2.
[("a", 4)]
["Germany"].Army("Tyr").Holds()
[(3, 1)]
[("b", 3)]
["Germany"].Army("Mun").Holds()
["Austria"].Army("Tyr").Holds()
[(2, 2)]
[("c", 2)]
["Germany"].Army("Mun").Holds()
["Austria"].Army("Tyr").MovesTo("Mun", s2_1).GetReference(out var tyr2_2);
["Austria"].Army("Tyr").MovesTo("Mun", b2).GetReference(out var tyr_c2);
// The attack on Mun 2:1 is repelled, but the support is cut.
// The attack on Mun b2 is repelled, but the support is cut.
setup.ValidateOrders();
Assert.That(tyr2_2, Is.Valid);
Assert.That(tyr_c2, Is.Valid);
setup.AdjudicateOrders();
Assert.That(tyr2_2, Is.Repelled);
Assert.That(mun2_1, Is.NotDislodged);
Assert.That(mun2_1, Is.Cut);
Assert.That(tyr_c2, Is.Repelled);
Assert.That(mun_b2, Is.NotDislodged);
Assert.That(mun_b2, Is.Cut);
// Though the support was cut, the timeline doesn't fork because the outcome of a battle
// wasn't changed in this timeline.
World world = setup.UpdateWorld();
Assert.That(
s3_0.Futures.Count(),
a3.Futures.Count(),
Is.EqualTo(1),
"A cross-timeline support cut incorrectly forked the timeline");
Assert.That(
s2_1.Futures.Count(),
b2.Futures.Count(),
Is.EqualTo(1),
"A cross-timeline support cut incorrectly forked the timeline");
}

View File

@ -186,7 +186,7 @@ public class MovementAdjudicatorTest
public void Update_DoubleHold()
{
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s1)
["Germany"]
.Army("Mun").Holds().GetReference(out var mun1);
@ -199,7 +199,7 @@ public class MovementAdjudicatorTest
World updated = setup.UpdateWorld();
// Confirm the future was created
Season s2 = updated.GetSeason(1, 0);
Season s2 = updated.GetSeason("a", 1);
Assert.That(s2.Past, Is.EqualTo(s1));
Assert.That(s2.Futures, Is.Empty);
Assert.That(s2.Timeline, Is.EqualTo(s1.Timeline));
@ -212,7 +212,7 @@ public class MovementAdjudicatorTest
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
Assert.That(u2.Season, Is.EqualTo(s2));
setup[(1, 0)]
setup[("a", 1)]
["Germany"]
.Army("Mun").Holds().GetReference(out var mun2);
@ -227,7 +227,7 @@ public class MovementAdjudicatorTest
// Update the world again
updated = setup.UpdateWorld();
Season s3 = updated.GetSeason(s2.Turn + 1, s2.Timeline);
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit));
}
@ -236,7 +236,7 @@ public class MovementAdjudicatorTest
public void Update_DoubleMove()
{
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
setup[(0, 0)]
setup[("a", 0)]
.GetReference(out Season s1)
["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var mun1);
@ -249,7 +249,7 @@ public class MovementAdjudicatorTest
World updated = setup.UpdateWorld();
// Confirm the future was created
Season s2 = updated.GetSeason(s1.Turn + 1, s1.Timeline);
Season s2 = updated.GetSeason(s1.Timeline, s1.Turn + 1);
Assert.That(s2.Past, Is.EqualTo(s1));
Assert.That(s2.Futures, Is.Empty);
Assert.That(s2.Timeline, Is.EqualTo(s1.Timeline));
@ -262,7 +262,7 @@ public class MovementAdjudicatorTest
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
Assert.That(u2.Season, Is.EqualTo(s2));
setup[(1, 0)]
setup[("a", 1)]
["Germany"]
.Army("Tyr").MovesTo("Mun").GetReference(out var tyr2);
@ -277,7 +277,7 @@ public class MovementAdjudicatorTest
// Update the world again
updated = setup.UpdateWorld();
Season s3 = updated.GetSeason(s2.Turn + 1, s2.Timeline);
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
Assert.That(u3.Past, Is.EqualTo(u2));
}

View File

@ -18,14 +18,14 @@ public class SeasonTests
Season c1 = a1.MakeFork();
Season d1 = a2.MakeFork();
Assert.That(a0.Timeline, Is.EqualTo(0), "Unexpected trunk timeline number");
Assert.That(a1.Timeline, Is.EqualTo(0), "Unexpected trunk timeline number");
Assert.That(a2.Timeline, Is.EqualTo(0), "Unexpected trunk timeline number");
Assert.That(a3.Timeline, Is.EqualTo(0), "Unexpected trunk timeline number");
Assert.That(b1.Timeline, Is.EqualTo(1), "Unexpected first alt number");
Assert.That(b2.Timeline, Is.EqualTo(1), "Unexpected first alt number");
Assert.That(c1.Timeline, Is.EqualTo(2), "Unexpected second alt number");
Assert.That(d1.Timeline, Is.EqualTo(3), "Unexpected third alt number");
Assert.That(a0.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a1.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a2.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a3.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(b1.Timeline, Is.EqualTo("b"), "Unexpected first alt");
Assert.That(b2.Timeline, Is.EqualTo("b"), "Unexpected first alt");
Assert.That(c1.Timeline, Is.EqualTo("c"), "Unexpected second alt");
Assert.That(d1.Timeline, Is.EqualTo("d"), "Unexpected third alt");
Assert.That(a0.Turn, Is.EqualTo(Season.FIRST_TURN + 0), "Unexpected first turn number");
Assert.That(a1.Turn, Is.EqualTo(Season.FIRST_TURN + 1), "Unexpected next turn number");
@ -57,9 +57,9 @@ public class SeasonTests
Season s4 = s2.MakeFork();
World updated = world.Update(seasons: world.Seasons.Append(s2).Append(s3).Append(s4));
Assert.That(updated.GetSeason(Season.FIRST_TURN, 0), Is.EqualTo(updated.RootSeason));
Assert.That(updated.GetSeason(Season.FIRST_TURN + 1, 0), Is.EqualTo(s2));
Assert.That(updated.GetSeason(Season.FIRST_TURN + 2, 0), Is.EqualTo(s3));
Assert.That(updated.GetSeason(Season.FIRST_TURN + 2, 1), Is.EqualTo(s4));
Assert.That(updated.GetSeason("a", Season.FIRST_TURN), Is.EqualTo(updated.RootSeason));
Assert.That(updated.GetSeason("a", Season.FIRST_TURN + 1), Is.EqualTo(s2));
Assert.That(updated.GetSeason("a", Season.FIRST_TURN + 2), Is.EqualTo(s3));
Assert.That(updated.GetSeason("b", Season.FIRST_TURN + 2), Is.EqualTo(s4));
}
}

View File

@ -19,7 +19,7 @@ public class TestCaseBuilder
/// <summary>
/// Choose a new season to define orders for.
/// </summary>
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
public ISeasonContext this[(string timeline, int turn) seasonCoord] { get; }
/// <summary>
/// Get the context for defining the orders for a power.
@ -40,7 +40,7 @@ public class TestCaseBuilder
/// <summary>
/// Choose a new season to define orders for.
/// </summary>
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
public ISeasonContext this[(string timeline, int turn) seasonCoord] { get; }
/// <summary>
/// Get the context for defining the orders for another power.
@ -188,7 +188,7 @@ public class TestCaseBuilder
/// <summary>
/// Choose a new season to define orders for.
/// </summary>
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
public ISeasonContext this[(string timeline, int turn) seasonCoord] { get; }
/// <summary>
/// Get the context for defining the orders for another power.
@ -234,13 +234,13 @@ public class TestCaseBuilder
/// <summary>
/// Get the context for defining the orders for a power. Defaults to the root season.
/// </summary>
public IPowerContext this[string powerName] => this[(0, 0)][powerName];
public IPowerContext this[string powerName] => this[("a", 0)][powerName];
/// <summary>
/// Get the context for defining the orders for a season.
/// </summary>
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> new SeasonContext(this, this.World.GetSeason(seasonCoord.turn, seasonCoord.timeline));
public ISeasonContext this[(string timeline, int turn) seasonCoord]
=> new SeasonContext(this, this.World.GetSeason(seasonCoord.timeline, seasonCoord.turn));
/// <summary>
/// Get a unit matching a description. If no such unit exists, one is created and added to the
@ -327,8 +327,8 @@ public class TestCaseBuilder
this.Season = season;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> this.Builder[(seasonCoord.turn, seasonCoord.timeline)];
public ISeasonContext this[(string timeline, int turn) seasonCoord]
=> this.Builder[(seasonCoord.timeline, seasonCoord.turn)];
public IPowerContext this[string powerName]
=> new PowerContext(this, this.Builder.World.Map.GetPower(powerName));
@ -353,7 +353,7 @@ public class TestCaseBuilder
this.Power = Power;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
public ISeasonContext this[(string timeline, int turn) seasonCoord]
=> this.SeasonContext[seasonCoord];
public IPowerContext this[string powerName]
@ -623,7 +623,7 @@ public class TestCaseBuilder
return this.Builder;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
public ISeasonContext this[(string timeline, int turn) seasonCoord]
=> this.SeasonContext[seasonCoord];
public IPowerContext this[string powerName]

View File

@ -14,7 +14,7 @@ class TestCaseBuilderTest
{
TestCaseBuilder setup = new(World.WithStandardMap());
Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count");
Assert.That(setup.World.Powers.Count, Is.EqualTo(7), "Unexpected power count");
Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet");
setup
@ -49,7 +49,7 @@ class TestCaseBuilderTest
{
TestCaseBuilder setup = new(World.WithStandardMap());
Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count");
Assert.That(setup.World.Powers.Count, Is.EqualTo(7), "Unexpected power count");
Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet");
Assert.That(setup.Orders, Is.Empty, "Expected no orders to be created yet");