Refactor World to avoid double enumeration
If an enumerable that created objects were passed, it would duplicate the objects when re-enumerated, which breaks all the reference equality logic.
This commit is contained in:
parent
18c5435c96
commit
b0a8100641
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace MultiversalDiplomacy.Model;
|
namespace MultiversalDiplomacy.Model;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -8,33 +10,33 @@ public class World
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The game map.
|
/// The game map.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Province> Provinces { get; }
|
public ReadOnlyCollection<Province> Provinces { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The game powers.
|
/// The game powers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Power> Powers { get; }
|
public ReadOnlyCollection<Power> Powers { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The state of the multiverse.
|
/// The state of the multiverse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Season> Seasons { get; }
|
public ReadOnlyCollection<Season> Seasons { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All units in the multiverse.
|
/// All units in the multiverse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Unit> Units { get; }
|
public ReadOnlyCollection<Unit> Units { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable game options.
|
/// Immutable game options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Options Options { get; }
|
public Options Options { get; }
|
||||||
|
|
||||||
public World(
|
private World(
|
||||||
IEnumerable<Province> provinces,
|
ReadOnlyCollection<Province> provinces,
|
||||||
IEnumerable<Power> powers,
|
ReadOnlyCollection<Power> powers,
|
||||||
IEnumerable<Season> seasons,
|
ReadOnlyCollection<Season> seasons,
|
||||||
IEnumerable<Unit> units,
|
ReadOnlyCollection<Unit> units,
|
||||||
Options options)
|
Options options)
|
||||||
{
|
{
|
||||||
this.Provinces = provinces;
|
this.Provinces = provinces;
|
||||||
|
@ -45,21 +47,105 @@ public class World
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new world with no map, powers, or units, and a root season.
|
/// Create a new world with specified provinces and powers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static World Empty => new World(
|
public static World WithMap(IEnumerable<Province> provinces, IEnumerable<Power> powers)
|
||||||
new List<Province>(),
|
=> new World(
|
||||||
new List<Power>(),
|
new(provinces.ToList()),
|
||||||
new List<Season> { Season.MakeRoot() },
|
new(powers.ToList()),
|
||||||
new List<Unit>(),
|
new(new List<Season>()),
|
||||||
|
new(new List<Unit>()),
|
||||||
new Options());
|
new Options());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a world with a standard map, powers, and initial unit placements.
|
/// Create a new world with the standard Diplomacy provinces and powers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static World Standard => Empty
|
public static World WithStandardMap()
|
||||||
|
=> WithMap(StandardProvinces, StandardPowers);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new world with new seasons.
|
||||||
|
/// </summary>
|
||||||
|
public World WithSeasons(IEnumerable<Season> seasons)
|
||||||
|
=> new World(this.Provinces, this.Powers, new(seasons.ToList()), this.Units, this.Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new world with an initial season.
|
||||||
|
/// </summary>
|
||||||
|
public World WithInitialSeason()
|
||||||
|
=> WithSeasons(new List<Season> { Season.MakeRoot() });
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new world with new units.
|
||||||
|
/// </summary>
|
||||||
|
public World WithUnits(IEnumerable<Unit> units)
|
||||||
|
=> new World(this.Provinces, this.Powers, this.Seasons, new(units.ToList()), this.Options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new world with new units created from unit specs. Units specs are in the format
|
||||||
|
/// "<power> <A/F> <province> [<coast>]". If the province or coast name has a space in it, the
|
||||||
|
/// abbreviation should be used.
|
||||||
|
/// </summary>
|
||||||
|
public World WithUnits(params string[] unitSpecs)
|
||||||
|
{
|
||||||
|
IEnumerable<Unit> units = unitSpecs.Select(spec =>
|
||||||
|
{
|
||||||
|
string[] splits = spec.Split(' ', 4);
|
||||||
|
Power power = this.GetPower(splits[0]);
|
||||||
|
UnitType type = splits[1] switch
|
||||||
|
{
|
||||||
|
"A" => UnitType.Army,
|
||||||
|
"F" => UnitType.Fleet,
|
||||||
|
_ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}")
|
||||||
|
};
|
||||||
|
Location location = type == UnitType.Army
|
||||||
|
? this.GetLand(splits[2])
|
||||||
|
: splits.Length == 3
|
||||||
|
? this.GetWater(splits[2])
|
||||||
|
: this.GetWater(splits[2], splits[3]);
|
||||||
|
Unit unit = Unit.Build(location, this.Seasons.First(), power, type);
|
||||||
|
return unit;
|
||||||
|
});
|
||||||
|
return this.WithUnits(units);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new world with standard Diplomacy initial unit placements.
|
||||||
|
/// </summary>
|
||||||
|
public World WithStandardUnits()
|
||||||
|
{
|
||||||
|
return this.WithUnits(
|
||||||
|
"Austria A Bud",
|
||||||
|
"Austria A Vir",
|
||||||
|
"Austria F Tri",
|
||||||
|
"England A Lvp",
|
||||||
|
"England F Edi",
|
||||||
|
"England F Lon",
|
||||||
|
"France A Mar",
|
||||||
|
"France A Par",
|
||||||
|
"France F Bre",
|
||||||
|
"Germany A Ber",
|
||||||
|
"Germany A Mun",
|
||||||
|
"Germany F Kie",
|
||||||
|
"Italy A Rom",
|
||||||
|
"Italy A Ven",
|
||||||
|
"Italy F Nap",
|
||||||
|
"Russia A Mos",
|
||||||
|
"Russia A War",
|
||||||
|
"Russia F Sev",
|
||||||
|
"Russia F Stp wc",
|
||||||
|
"Turkey A Con",
|
||||||
|
"Turkey A Smy",
|
||||||
|
"Turkey F Ank"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A standard Diplomacy game setup.
|
||||||
|
/// </summary>
|
||||||
|
public static World Standard => World
|
||||||
.WithStandardMap()
|
.WithStandardMap()
|
||||||
.WithStandardPowers()
|
.WithInitialSeason()
|
||||||
.WithStandardUnits();
|
.WithStandardUnits();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -68,7 +154,7 @@ public class World
|
||||||
private Province GetProvince(string provinceName)
|
private Province GetProvince(string provinceName)
|
||||||
{
|
{
|
||||||
string provinceNameUpper = provinceName.ToUpperInvariant();
|
string provinceNameUpper = provinceName.ToUpperInvariant();
|
||||||
Province? foundProvince = this.Provinces.FirstOrDefault(
|
Province? foundProvince = this.Provinces.SingleOrDefault(
|
||||||
p => p != null &&
|
p => p != null &&
|
||||||
(p.Name.ToUpperInvariant() == provinceNameUpper
|
(p.Name.ToUpperInvariant() == provinceNameUpper
|
||||||
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
|
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
|
||||||
|
@ -106,11 +192,11 @@ public class World
|
||||||
: GetLocation(provinceName, l => l.Name == coastName || l.Abbreviation == coastName);
|
: GetLocation(provinceName, l => l.Name == coastName || l.Abbreviation == coastName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a power by name. Throws if the power is not found.
|
/// Get a power by name. Throws if there is not exactly one such power.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Power GetPower(string powerName)
|
public Power GetPower(string powerName)
|
||||||
{
|
{
|
||||||
Power? foundPower = this.Powers.FirstOrDefault(
|
Power? foundPower = this.Powers.SingleOrDefault(
|
||||||
p =>
|
p =>
|
||||||
p != null
|
p != null
|
||||||
&& (p.Name == powerName || p.Name.StartsWith(powerName)),
|
&& (p.Name == powerName || p.Name.StartsWith(powerName)),
|
||||||
|
@ -121,19 +207,23 @@ public class World
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new world from this one with new provinces.
|
/// Returns a unit in a province. Throws if there is not exactly one such unit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public World WithMap(IEnumerable<Province> provinces)
|
public Unit? GetUnitAt(string provinceName)
|
||||||
{
|
{
|
||||||
if (this.Units.Any()) throw new InvalidOperationException(
|
Province province = GetProvince(provinceName);
|
||||||
"Provinces cannot be changed once units have been placed on the map");
|
Unit? foundUnit = this.Units.SingleOrDefault(
|
||||||
return new World(provinces, this.Powers, this.Seasons, this.Units, this.Options);
|
u => u != null && u.Location.Province == province,
|
||||||
|
null);
|
||||||
|
return foundUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new world from this one with the standard Diplomacy provinces.
|
/// The standard Diplomacy provinces.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public World WithStandardMap()
|
public static ReadOnlyCollection<Province> StandardProvinces
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
// Define the provinces of the standard world map.
|
// Define the provinces of the standard world map.
|
||||||
List<Province> standardProvinces = new List<Province>
|
List<Province> standardProvinces = new List<Province>
|
||||||
|
@ -440,97 +530,24 @@ public class World
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
return this.WithMap(standardProvinces);
|
return new(standardProvinces);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new world from this one with new powers.
|
/// The standard Diplomacy powers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public World WithPowers(IEnumerable<Power> powers)
|
public static ReadOnlyCollection<Power> StandardPowers
|
||||||
=> new World(this.Provinces, powers, this.Seasons, this.Units, this.Options);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with new powers created with the given names.
|
|
||||||
/// </summary>
|
|
||||||
public World WithPowers(IEnumerable<string> powerNames)
|
|
||||||
=> WithPowers(powerNames.Select(name => new Model.Power(name)));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with new powers created with the given names.
|
|
||||||
/// </summary>
|
|
||||||
public World WithPowers(params string[] powerNames)
|
|
||||||
=> WithPowers(powerNames.AsEnumerable());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with the standard Diplomacy powers.
|
|
||||||
/// </summary>
|
|
||||||
public World WithStandardPowers()
|
|
||||||
=> WithPowers("Austria", "England", "France", "Germany", "Italy", "Russia", "Turkey");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with new units created from unit specs. Units specs are
|
|
||||||
/// in the format "<power> <A/F> <province> [<coast>]". If the province or coast name has a
|
|
||||||
/// space in it, the abbreviation should be used.
|
|
||||||
/// </summary>
|
|
||||||
public World WithUnits(IEnumerable<string> unitSpec)
|
|
||||||
{
|
{
|
||||||
IEnumerable<Unit> units = unitSpec.Select(spec =>
|
get => new(new List<Power>
|
||||||
{
|
{
|
||||||
string[] splits = spec.Split(' ', 4);
|
new Power("Austria"),
|
||||||
Power power = this.GetPower(splits[0]);
|
new Power("England"),
|
||||||
UnitType type = splits[1] switch
|
new Power("France"),
|
||||||
{
|
new Power("Germany"),
|
||||||
"A" => UnitType.Army,
|
new Power("Italy"),
|
||||||
"F" => UnitType.Fleet,
|
new Power("Russia"),
|
||||||
_ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}")
|
new Power("Turkey"),
|
||||||
};
|
|
||||||
Location location = type == UnitType.Army
|
|
||||||
? this.GetLand(splits[2])
|
|
||||||
: splits.Length == 3
|
|
||||||
? this.GetWater(splits[2])
|
|
||||||
: this.GetWater(splits[2], splits[3]);
|
|
||||||
Unit unit = Unit.Build(location, this.Seasons.First(), power, type);
|
|
||||||
return unit;
|
|
||||||
});
|
});
|
||||||
return new World(this.Provinces, this.Powers, this.Seasons, units, this.Options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with new units created from unit specs. Units specs are
|
|
||||||
/// in the format "<power> <A/F> <province> [<coast>]".
|
|
||||||
/// </summary>
|
|
||||||
public World WithUnits(params string[] unitSpec)
|
|
||||||
=> this.WithUnits(unitSpec.AsEnumerable());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new world from this one with new units created according to the standard Diplomacy
|
|
||||||
/// initial unit deployments.
|
|
||||||
/// </summary>
|
|
||||||
public World WithStandardUnits()
|
|
||||||
{
|
|
||||||
return this.WithUnits(
|
|
||||||
"Austria A Bud",
|
|
||||||
"Austria A Vir",
|
|
||||||
"Austria F Tri",
|
|
||||||
"England A Lvp",
|
|
||||||
"England F Edi",
|
|
||||||
"England F Lon",
|
|
||||||
"France A Mar",
|
|
||||||
"France A Par",
|
|
||||||
"France F Bre",
|
|
||||||
"Germany A Ber",
|
|
||||||
"Germany A Mun",
|
|
||||||
"Germany F Kie",
|
|
||||||
"Italy A Rom",
|
|
||||||
"Italy A Ven",
|
|
||||||
"Italy F Nap",
|
|
||||||
"Russia A Mos",
|
|
||||||
"Russia A War",
|
|
||||||
"Russia F Sev",
|
|
||||||
"Russia F Stp wc",
|
|
||||||
"Turkey A Con",
|
|
||||||
"Turkey A Smy",
|
|
||||||
"Turkey F Ank"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,8 +15,8 @@ public class AdjudicatorTests
|
||||||
{
|
{
|
||||||
return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
|
return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
|
||||||
});
|
});
|
||||||
World world = World.Empty.WithPowers("Power");
|
World world = World.WithStandardMap().WithInitialSeason();
|
||||||
Power power = world.GetPower("Power");
|
Power power = world.GetPower("Austria");
|
||||||
|
|
||||||
Order order = new NullOrder(power);
|
Order order = new NullOrder(power);
|
||||||
List<Order> orders = new List<Order> { order };
|
List<Order> orders = new List<Order> { order };
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class MapTests
|
||||||
[Test]
|
[Test]
|
||||||
public void LandAndSeaBorders()
|
public void LandAndSeaBorders()
|
||||||
{
|
{
|
||||||
World map = World.Empty.WithStandardMap();
|
World map = World.WithStandardMap();
|
||||||
Assert.That(
|
Assert.That(
|
||||||
map.GetLand("NAF").Adjacents.Count(),
|
map.GetLand("NAF").Adjacents.Count(),
|
||||||
Is.EqualTo(1),
|
Is.EqualTo(1),
|
||||||
|
|
|
@ -9,9 +9,11 @@ public class UnitTests
|
||||||
[Test]
|
[Test]
|
||||||
public void MovementTest()
|
public void MovementTest()
|
||||||
{
|
{
|
||||||
World world = World.Empty.WithStandardMap().WithPowers("First");
|
World world = World.WithStandardMap().WithInitialSeason();
|
||||||
Location Mun = world.GetLand("Mun"), Boh = world.GetLand("Boh"), Tyr = world.GetLand("Tyr");
|
Location Mun = world.GetLand("Mun"),
|
||||||
Power pw1 = world.GetPower("First");
|
Boh = world.GetLand("Boh"),
|
||||||
|
Tyr = world.GetLand("Tyr");
|
||||||
|
Power pw1 = world.GetPower("Austria");
|
||||||
Season s1 = world.Seasons.First();
|
Season s1 = world.Seasons.First();
|
||||||
Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army);
|
Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue