Refactor timeline factory to generate string ids

The strings are immediately shimmed back to ints for now
This commit is contained in:
Tim Van Baak 2024-08-12 07:33:17 -07:00
parent 40254b0fca
commit 780ae8b948
4 changed files with 96 additions and 12 deletions

View File

@ -5,16 +5,6 @@ namespace MultiversalDiplomacy.Model;
/// </summary> /// </summary>
public class Season public class Season
{ {
/// <summary>
/// A shared counter for handing out new timeline numbers.
/// </summary>
private class TimelineFactory
{
private int nextTimeline = 0;
public int NextTimeline() => nextTimeline++;
}
/// <summary> /// <summary>
/// The first turn number. /// The first turn number.
/// </summary> /// </summary>
@ -83,7 +73,7 @@ public class Season
return new Season( return new Season(
past: null, past: null,
turn: FIRST_TURN, turn: FIRST_TURN,
timeline: factory.NextTimeline(), timeline: TimelineFactory.StringToInt(factory.NextTimeline()),
factory: factory); factory: factory);
} }
@ -97,7 +87,7 @@ public class Season
/// Create a season immediately after this one in a new timeline. /// Create a season immediately after this one in a new timeline.
/// </summary> /// </summary>
public Season MakeFork() public Season MakeFork()
=> new Season(this, this.Turn + 1, this.Timelines.NextTimeline(), this.Timelines); => new Season(this, this.Turn + 1, TimelineFactory.StringToInt(Timelines.NextTimeline()), this.Timelines);
/// <summary> /// <summary>
/// Returns the first season in this season's timeline. The first season is the /// Returns the first season in this season's timeline. The first season is the

View File

@ -0,0 +1,50 @@
namespace MultiversalDiplomacy.Model;
/// <summary>
/// A shared counter for handing out new timeline designations.
/// </summary>
internal class TimelineFactory
{
private static readonly char[] Letters = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
];
/// <summary>
/// Convert a string timeline identifier to its serial number.
/// </summary>
/// <param name="timeline">Timeline identifier.</param>
/// <returns>Integer.</returns>
public static int StringToInt(string timeline)
{
int result = Array.IndexOf(Letters, timeline[0]);
for (int i = 1; i < timeline.Length; i++) {
// The result is incremented by one because timeline designations are not a true base26 system.
// The "ones digit" maps a-z 0-25, but the "tens digit" maps a to 1, so "10" (26) is "aa" and not "a0"
result = (result + 1) * 26;
result += Array.IndexOf(Letters, timeline[i]);
}
return result;
}
/// <summary>
/// Convert a timeline serial number to its string identifier.
/// </summary>
/// <param name="designation">Integer.</param>
/// <returns>Timeline identifier.</returns>
public static string IntToString(int designation) {
static int downshift(int i ) => (i - (i % 26)) / 26;
IEnumerable<char> result = [Letters[designation % 26]];
for (int remainder = downshift(designation); remainder > 0; remainder = downshift(remainder) - 1) {
// We subtract 1 after downshifting for the same reason we add 1 above after upshifting.
//
result = result.Prepend(Letters[(remainder % 26 + 25) % 26]);
}
return new string(result.ToArray());
}
private int nextTimeline = 0;
public string NextTimeline() => IntToString(nextTimeline++);
}

View File

@ -7,4 +7,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>MultiversalDiplomacyTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,38 @@
using MultiversalDiplomacy.Model;
using NUnit.Framework;
namespace MultiversalDiplomacyTests.Model;
public class TimelineFactoryTest
{
[TestCase(0, "a")]
[TestCase(1, "b")]
[TestCase(25, "z")]
[TestCase(26, "aa")]
[TestCase(27, "ab")]
[TestCase(51, "az")]
[TestCase(52, "ba")]
[TestCase(53, "bb")]
[TestCase(77, "bz")]
[TestCase(78, "ca")]
public void RoundTripTimelineDesignations(int number, string designation)
{
Assert.That(TimelineFactory.IntToString(number), Is.EqualTo(designation), "Incorrect string");
Assert.That(TimelineFactory.StringToInt(designation), Is.EqualTo(number), "Incorrect number");
}
[Test]
public void NoSharedFactoryState()
{
TimelineFactory one = new();
TimelineFactory two = new();
Assert.That(one.NextTimeline(), Is.EqualTo("a"));
Assert.That(one.NextTimeline(), Is.EqualTo("b"));
Assert.That(one.NextTimeline(), Is.EqualTo("c"));
Assert.That(two.NextTimeline(), Is.EqualTo("a"));
Assert.That(two.NextTimeline(), Is.EqualTo("b"));
}
}