Add the twelve contests left in the station's log
REF, the Ukrainian DX contest, the BARTG Sprint, ARRL 10M, Russian DX RTTY, RDAC, YO DX HF, Oceania DX, IARU Region 1 Field Day, SARTG RTTY, All Asian and SA 10. Every contest name in the log now opens, and every one of these but Field Day scores its log contact for contact as N1MM did. Most are written from N1MM's own class and checked against the log. Three things the log settled that the class does not say plainly: ARRL 10M counts its multipliers once per mode rather than once, the BARTG Sprint counts a continent once in the contest while its countries and call areas count per band, and RDAC counts nothing but the districts and scores nothing for a station that sends none. All Asian and SA 10 have no class in N1MM, which logs them from a .udc file, so those two are written from the published rules and the log. docs/unfinished.md lists what is still not settled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -24,6 +24,18 @@ public sealed class ContestRegistry
|
||||
new("EUDXC", "EU DX Contest", [ModeCategory.Cw, ModeCategory.Phone], _ => new EuDxContest()),
|
||||
new("CQ160", "CQ World-Wide 160 Metre", [ModeCategory.Cw, ModeCategory.Phone], m => new CqOneSixty(m)),
|
||||
new("XERTTY", "Mexico RTTY Contest", [ModeCategory.Digital], _ => new MexicoRtty()),
|
||||
new("REF", "French REF DX contest", [ModeCategory.Cw, ModeCategory.Phone], m => new FrenchRef(m)),
|
||||
new("UKRAINDX", "Ukrainian DX", [ModeCategory.Cw, ModeCategory.Phone], _ => new UkrainianDx()),
|
||||
new("BARTGRTTYS", "BARTG RTTY Sprint", [ModeCategory.Digital], _ => new BartgSprint()),
|
||||
new("ARRL10M", "ARRL 10 Metre Contest", [ModeCategory.Cw, ModeCategory.Phone], _ => new Arrl10Metre()),
|
||||
new("RUSDXRTTY", "Russian DX RTTY", [ModeCategory.Digital], _ => new RussianDxRtty()),
|
||||
new("RDAC", "Russian District Award Contest", [ModeCategory.Cw, ModeCategory.Phone], _ => new RussianDistrictAward()),
|
||||
new("YOHFDX", "YO DX HF", [ModeCategory.Cw, ModeCategory.Phone], _ => new YoDxHf()),
|
||||
new("OCEANIA", "Oceania DX", [ModeCategory.Cw, ModeCategory.Phone], m => new OceaniaDx(m)),
|
||||
new("FDREG1", "IARU Region 1 Field Day", [ModeCategory.Cw, ModeCategory.Phone], _ => new FieldDayRegionOne()),
|
||||
new("SARTGRTTY", "SARTG World Wide RTTY", [ModeCategory.Digital], _ => new SartgRtty()),
|
||||
new("ALLASIA", "All Asian DX", [ModeCategory.Cw, ModeCategory.Phone], m => new AllAsian(m)),
|
||||
new("SA10", "SA 10 Metre Contest", [ModeCategory.Cw, ModeCategory.Phone], _ => new SouthAmerica10()),
|
||||
new("DX", "General logging", [], _ => new GeneralLogging()),
|
||||
];
|
||||
|
||||
|
||||
@@ -39,6 +39,14 @@ public static class N1mmContestNames
|
||||
["CQ160CW"] = ("CQ160", ModeCategory.Cw),
|
||||
["CQ160SSB"] = ("CQ160", ModeCategory.Phone),
|
||||
["EU_DXC"] = ("EUDXC", ModeCategory.Cw),
|
||||
["REFCW"] = ("REF", ModeCategory.Cw),
|
||||
["REFSSB"] = ("REF", ModeCategory.Phone),
|
||||
["OCEANIACW"] = ("OCEANIA", ModeCategory.Cw),
|
||||
["OCEANIASSB"] = ("OCEANIA", ModeCategory.Phone),
|
||||
["BARTGSRTTY"] = ("BARTGRTTYS", ModeCategory.Digital),
|
||||
["ALLASIACW"] = ("ALLASIA", ModeCategory.Cw),
|
||||
["ALLASIASSB"] = ("ALLASIA", ModeCategory.Phone),
|
||||
["SA10_DX"] = ("SA10", ModeCategory.Phone),
|
||||
};
|
||||
|
||||
public static bool TryResolve(string name, out string family, out ModeCategory mode)
|
||||
|
||||
72
src/Nonemm.Contests/Rules/AllAsian.cs
Normal file
72
src/Nonemm.Contests/Rules/AllAsian.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The JARL All Asian DX contest. Only contacts with Asia count for a station
|
||||
/// outside it. The exchange is the operator's age, the points go by band, and
|
||||
/// each prefix worked counts once per band.
|
||||
public sealed class AllAsian : Contest
|
||||
{
|
||||
private readonly ModeCategory mode;
|
||||
|
||||
public AllAsian(ModeCategory mode) => this.mode = mode;
|
||||
|
||||
public string Name => mode == ModeCategory.Cw ? "ALLASIACW" : "ALLASIASSB";
|
||||
|
||||
public string DisplayName => $"All Asian DX {ModeLabel()}";
|
||||
|
||||
public string CabrilloName => mode == ModeCategory.Cw ? "AA-CW" : "AA-SSB";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Age", ExchangeSlot.Exchange1, ExchangeFieldKind.Number) { Width = 3 },
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Prefixes"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => false;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [mode];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => "";
|
||||
|
||||
public int PointsFor(QsoContext qso) => IsWorkable(qso) ? PointsOn(qso.Band) : 0;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso) =>
|
||||
IsWorkable(qso) && qso.Qso.Call.WpxPrefix() is { } prefix
|
||||
? [new Multiplier(1, prefix, qso.Band?.Name ?? "")]
|
||||
: [];
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(entry.SentExchange, 3),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Exchange1, 3),
|
||||
]);
|
||||
|
||||
/// One side of every contact has to be in Asia.
|
||||
private static bool IsWorkable(QsoContext qso) =>
|
||||
qso.Continent == "AS" || qso.Me.Continent == "AS";
|
||||
|
||||
private static int PointsOn(Band? band) => band?.Name switch
|
||||
{
|
||||
"160M" or "80M" => 3,
|
||||
"40M" => 2,
|
||||
null => 0,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
private string ModeLabel() => mode == ModeCategory.Cw ? "CW" : "SSB";
|
||||
}
|
||||
79
src/Nonemm.Contests/Rules/Arrl10Metre.cs
Normal file
79
src/Nonemm.Contests/Rules/Arrl10Metre.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Nonemm.Contests.Multipliers;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The ARRL 10 metre contest. A CW contact is 4 points and a phone contact 2.
|
||||
/// US, Canadian and Mexican stations send a state or province, everyone else a
|
||||
/// serial number, and both the states and the countries are multipliers, each
|
||||
/// counted once per mode: the contest is one band, and CW and phone count
|
||||
/// separately.
|
||||
public sealed class Arrl10Metre : Contest
|
||||
{
|
||||
public string Name => "ARRL10M";
|
||||
|
||||
public string DisplayName => "ARRL 10 Metre Contest";
|
||||
|
||||
public string CabrilloName => "ARRL-10";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("S/P", ExchangeSlot.Section, ExchangeFieldKind.UsStateOrCanadianProvince),
|
||||
new ExchangeField("Nr", ExchangeSlot.Exchange1, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public bool SkipsField(ExchangeField field, CountryLookup? their) => field.Slot switch
|
||||
{
|
||||
ExchangeSlot.Section => their is not null && !SendsState(their.Entity.PrimaryPrefix),
|
||||
ExchangeSlot.Exchange1 => their is not null && SendsState(their.Entity.PrimaryPrefix),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["States", "Countries"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerMode;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) =>
|
||||
SendsState(me.CountryPrefix)
|
||||
? me.State.Length > 0 ? me.State : me.Province
|
||||
: "001";
|
||||
|
||||
public int PointsFor(QsoContext qso) => qso.ModeCategory == ModeCategory.Cw ? 4 : 2;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string mode = qso.ModeCategory.ToString();
|
||||
string area = StatesAndProvinces.ArrlDxArea(qso.Qso.Section);
|
||||
if (StatesAndProvinces.ArrlDxMultipliers.Contains(area))
|
||||
{
|
||||
return [new Multiplier(1, area, mode)];
|
||||
}
|
||||
return qso.CountryPrefix.Length == 0 ? [] : [new Multiplier(2, qso.CountryPrefix, mode)];
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(SentExchangeFor(me), 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section.Length > 0 ? qso.Section : qso.Exchange1, 6),
|
||||
]);
|
||||
|
||||
/// The states and provinces of the US, Canada and Mexico are the exchange;
|
||||
/// the rest of the world sends a number.
|
||||
private static bool SendsState(string countryPrefix) => countryPrefix is "K" or "VE" or "XE";
|
||||
}
|
||||
71
src/Nonemm.Contests/Rules/BartgSprint.cs
Normal file
71
src/Nonemm.Contests/Rules/BartgSprint.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The BARTG RTTY Sprint. Every contact is one point; the multipliers are the
|
||||
/// countries and the US and Canadian call areas once per band, and the
|
||||
/// continents once in the contest.
|
||||
///
|
||||
/// The exchange is a serial number. N1MM also writes the time of the contact
|
||||
/// into the exchange column, which the Cabrillo line carries.
|
||||
public sealed class BartgSprint : Contest
|
||||
{
|
||||
public string Name => "BARTGRTTYS";
|
||||
|
||||
public string DisplayName => "BARTG RTTY Sprint";
|
||||
|
||||
public string CabrilloName => "BARTG-SPRINT";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("Nr", ExchangeSlot.SerialNumber, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Areas", "Continents"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Digital];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => "001";
|
||||
|
||||
public int PointsFor(QsoContext qso) => 1;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
List<Multiplier> found = [];
|
||||
if (qso.CountryPrefix.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(1, qso.CountryPrefix, band));
|
||||
}
|
||||
string area = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
if (area.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, area, band));
|
||||
}
|
||||
if (qso.Continent.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(3, qso.Continent, ""));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField($"{qso.SentNumber:000}", 4),
|
||||
new CabrilloField(qso.TimestampUtc.ToString("HHmm"), 4),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField($"{qso.ReceivedNumber:000}", 4),
|
||||
new CabrilloField(qso.Exchange1, 4),
|
||||
]);
|
||||
}
|
||||
72
src/Nonemm.Contests/Rules/FieldDayRegionOne.cs
Normal file
72
src/Nonemm.Contests/Rules/FieldDayRegionOne.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The IARU Region 1 Field Day. A portable station is worth 5 points, a station
|
||||
/// in Region 1 operating from home 2, and one outside Region 1 3. The exchange
|
||||
/// is a serial number and the countries are multipliers, once per band.
|
||||
public sealed class FieldDayRegionOne : Contest
|
||||
{
|
||||
public string Name => "FDREG1";
|
||||
|
||||
public string DisplayName => "IARU Region 1 Field Day";
|
||||
|
||||
public string CabrilloName => "FIELDDAY-REGION-1";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Nr", ExchangeSlot.SerialNumber, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBandAndMode;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => "001";
|
||||
|
||||
public int PointsFor(QsoContext qso)
|
||||
{
|
||||
if (IsPortable(qso.Qso.Call))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
return IsRegionOne(qso.Continent) ? 2 : 3;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso) =>
|
||||
qso.CountryPrefix.Length == 0
|
||||
? []
|
||||
: [new Multiplier(1, qso.CountryPrefix, qso.Band?.Name ?? "")];
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField($"{qso.SentNumber:000}", 4),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField($"{qso.ReceivedNumber:000}", 4),
|
||||
]);
|
||||
|
||||
/// A field day station signs portable, mobile or /A.
|
||||
private static bool IsPortable(Callsign call) =>
|
||||
call.Text.EndsWith("/P", StringComparison.OrdinalIgnoreCase)
|
||||
|| call.Text.EndsWith("/M", StringComparison.OrdinalIgnoreCase)
|
||||
|| call.Text.EndsWith("/A", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// Region 1 is Europe, Africa and the Middle East; the country file names
|
||||
/// the first two, and Asia is split between regions, which the continent
|
||||
/// alone cannot say.
|
||||
private static bool IsRegionOne(string continent) => continent is "EU" or "AF";
|
||||
}
|
||||
104
src/Nonemm.Contests/Rules/FrenchRef.cs
Normal file
104
src/Nonemm.Contests/Rules/FrenchRef.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The French REF DX contest, CW and phone. A French station sends its
|
||||
/// department, everyone else a serial number. For a station outside France a
|
||||
/// contact is 1 point on its own continent and 3 from another, and the
|
||||
/// multipliers are the French departments and the French entities overseas.
|
||||
public sealed class FrenchRef : Contest
|
||||
{
|
||||
/// France and everything the contest counts as French: Corsica, the
|
||||
/// overseas departments and territories, and the Antarctic bases.
|
||||
private static readonly IReadOnlySet<string> French = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"F", "TK", "FG", "FH", "FJ", "FM", "FP", "FS", "FW", "FY", "FK", "TX0", "FO", "FR",
|
||||
"FT5X", "FT5Y", "FT5Z", "FT5W", "FT8X", "FT8Y", "FT8Z", "FT8W", "CE9",
|
||||
};
|
||||
|
||||
private readonly ModeCategory mode;
|
||||
|
||||
public FrenchRef(ModeCategory mode) => this.mode = mode;
|
||||
|
||||
public string Name => mode == ModeCategory.Cw ? "REFCW" : "REFSSB";
|
||||
|
||||
public string DisplayName => $"French REF DX contest {ModeLabel()}";
|
||||
|
||||
public string CabrilloName => mode == ModeCategory.Cw ? "REF-CW" : "REF-SSB";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Dept", ExchangeSlot.Section, ExchangeFieldKind.Text) { Width = 3 },
|
||||
new ExchangeField("Nr", ExchangeSlot.Exchange1, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public bool SkipsField(ExchangeField field, CountryLookup? their) => field.Slot switch
|
||||
{
|
||||
ExchangeSlot.Section => their is not null && their.Entity.PrimaryPrefix != "F",
|
||||
ExchangeSlot.Exchange1 => their is not null && their.Entity.PrimaryPrefix == "F",
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Mults"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [mode];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) =>
|
||||
me.CountryPrefix == "F" ? me.County : "001";
|
||||
|
||||
/// A French operator gets 6 points for another French station on its own
|
||||
/// continent and 15 from another, and 1 or 2 points for everyone else.
|
||||
public int PointsFor(QsoContext qso)
|
||||
{
|
||||
if (IsFrench(qso.Me.CountryPrefix))
|
||||
{
|
||||
return IsFrench(qso.CountryPrefix)
|
||||
? qso.IsSameContinent ? 6 : 15
|
||||
: qso.IsSameContinent ? 1 : 2;
|
||||
}
|
||||
return qso.IsSameContinent ? 1 : 3;
|
||||
}
|
||||
|
||||
/// A French operator counts countries; everyone else counts the French
|
||||
/// departments and the French entities overseas, each once per band.
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
if (IsFrench(qso.Me.CountryPrefix))
|
||||
{
|
||||
return qso.CountryPrefix.Length == 0 ? [] : [new Multiplier(1, qso.CountryPrefix, band)];
|
||||
}
|
||||
if (qso.CountryPrefix == "F")
|
||||
{
|
||||
string department = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
return department.Length == 0 ? [] : [new Multiplier(1, department, band)];
|
||||
}
|
||||
return IsFrench(qso.CountryPrefix) ? [new Multiplier(1, qso.CountryPrefix, band)] : [];
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(me.CountryPrefix == "F" ? me.County : $"{qso.SentNumber:000}", 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section.Length > 0 ? qso.Section : qso.Exchange1, 6),
|
||||
]);
|
||||
|
||||
private static bool IsFrench(string countryPrefix) => French.Contains(countryPrefix);
|
||||
|
||||
private string ModeLabel() => mode == ModeCategory.Cw ? "CW" : "SSB";
|
||||
}
|
||||
75
src/Nonemm.Contests/Rules/OceaniaDx.cs
Normal file
75
src/Nonemm.Contests/Rules/OceaniaDx.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The Oceania DX contest, CW and phone. Only contacts with Oceania count for
|
||||
/// a station outside it, and they are worth more the lower the band: 20 points
|
||||
/// on 160 metres, 10 on 80, 5 on 40 and 1 on the higher bands. Each prefix
|
||||
/// worked counts once per band.
|
||||
public sealed class OceaniaDx : Contest
|
||||
{
|
||||
private readonly ModeCategory mode;
|
||||
|
||||
public OceaniaDx(ModeCategory mode) => this.mode = mode;
|
||||
|
||||
public string Name => mode == ModeCategory.Cw ? "OceaniaCW" : "OceaniaSSB";
|
||||
|
||||
public string DisplayName => $"Oceania DX {ModeLabel()}";
|
||||
|
||||
public string CabrilloName => mode == ModeCategory.Cw ? "Oceania-DX-CW" : "Oceania-DX-SSB";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Nr", ExchangeSlot.SerialNumber, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Prefixes"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [mode];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => "001";
|
||||
|
||||
public int PointsFor(QsoContext qso) =>
|
||||
IsWorkable(qso) ? PointsOn(qso.Band) : 0;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso) =>
|
||||
IsWorkable(qso) && qso.Qso.Call.WpxPrefix() is { } prefix
|
||||
? [new Multiplier(1, prefix, qso.Band?.Name ?? "")]
|
||||
: [];
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField($"{qso.SentNumber:000}", 4),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField($"{qso.ReceivedNumber:000}", 4),
|
||||
]);
|
||||
|
||||
/// One side of every contact has to be in Oceania.
|
||||
private static bool IsWorkable(QsoContext qso) =>
|
||||
qso.Continent == "OC" || qso.Me.Continent == "OC";
|
||||
|
||||
private static int PointsOn(Band? band) => band?.Name switch
|
||||
{
|
||||
"160M" => 20,
|
||||
"80M" => 10,
|
||||
"40M" => 5,
|
||||
null => 0,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
private string ModeLabel() => mode == ModeCategory.Cw ? "CW" : "SSB";
|
||||
}
|
||||
62
src/Nonemm.Contests/Rules/RussianDistrictAward.cs
Normal file
62
src/Nonemm.Contests/Rules/RussianDistrictAward.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The Russian District Award contest. Only a station that sends a district
|
||||
/// code counts: it is worth 10 points and its district is a multiplier once
|
||||
/// per band. Everyone else is worth nothing.
|
||||
public sealed class RussianDistrictAward : Contest
|
||||
{
|
||||
public string Name => "RDAC";
|
||||
|
||||
public string DisplayName => "Russian District Award Contest";
|
||||
|
||||
public string CabrilloName => "RDAC";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("RDA", ExchangeSlot.Section, ExchangeFieldKind.Text) { Width = 5 },
|
||||
];
|
||||
|
||||
/// N1MM names a country multiplier for this contest and never counts one,
|
||||
/// and the score summary shows both, so the districts stay in the second
|
||||
/// column here too.
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Districts"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBandAndMode;
|
||||
|
||||
public bool HasSerialNumbers => false;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => me.County;
|
||||
|
||||
public int PointsFor(QsoContext qso) => District(qso.Qso).Length > 0 ? 10 : 0;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string district = District(qso.Qso);
|
||||
return district.Length == 0
|
||||
? []
|
||||
: [new Multiplier(2, district, qso.Band?.Name ?? "")];
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(me.County, 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(District(qso), 6),
|
||||
]);
|
||||
|
||||
private static string District(Qso qso) => qso.Section.Trim().ToUpperInvariant();
|
||||
}
|
||||
71
src/Nonemm.Contests/Rules/RussianDxRtty.cs
Normal file
71
src/Nonemm.Contests/Rules/RussianDxRtty.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The Russian DX RTTY contest. A Russian station sends its oblast, everyone
|
||||
/// else their CQ zone. A contact on your own continent is 5 points and one
|
||||
/// from another 10, and the countries and the oblasts are multipliers, each
|
||||
/// once per band.
|
||||
public sealed class RussianDxRtty : Contest
|
||||
{
|
||||
public string Name => "RUSDXRTTY";
|
||||
|
||||
public string DisplayName => "Russian DX RTTY";
|
||||
|
||||
public string CabrilloName => "RADIO-WW-RTTY";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Oblast/Zone", ExchangeSlot.Section, ExchangeFieldKind.Text) { Width = 3 },
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Oblasts"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => false;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Digital];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) =>
|
||||
IsRussian(me.CountryPrefix) ? me.County : me.CqZone.ToString();
|
||||
|
||||
public int PointsFor(QsoContext qso) => qso.IsSameContinent ? 5 : 10;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
List<Multiplier> found = [];
|
||||
if (qso.CountryPrefix.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(1, qso.CountryPrefix, band));
|
||||
}
|
||||
string oblast = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
if (IsRussian(qso.CountryPrefix) && oblast.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, oblast, band));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(SentExchangeFor(me), 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section, 6),
|
||||
]);
|
||||
|
||||
private static bool IsRussian(string countryPrefix) =>
|
||||
countryPrefix is "UA" or "UA2" or "UA9";
|
||||
}
|
||||
66
src/Nonemm.Contests/Rules/SartgRtty.cs
Normal file
66
src/Nonemm.Contests/Rules/SartgRtty.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The SARTG World Wide RTTY contest. A contact inside your own country is 5
|
||||
/// points, one on your own continent 10 and one from another 15. The countries
|
||||
/// count once per band, and so do the US, Canadian and Japanese call areas.
|
||||
public sealed class SartgRtty : Contest
|
||||
{
|
||||
public string Name => "SARTGRTTY";
|
||||
|
||||
public string DisplayName => "SARTG World Wide RTTY";
|
||||
|
||||
public string CabrilloName => "SARTG-RTTY";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Nr", ExchangeSlot.SerialNumber, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Areas"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBand;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Digital];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => "001";
|
||||
|
||||
public int PointsFor(QsoContext qso) =>
|
||||
qso.IsSameCountry ? 5 : qso.IsSameContinent ? 10 : 15;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
List<Multiplier> found = [];
|
||||
if (qso.CountryPrefix.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(1, qso.CountryPrefix, band));
|
||||
}
|
||||
string area = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
if (area.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, area, band));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField($"{qso.SentNumber:000}", 4),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField($"{qso.ReceivedNumber:000}", 4),
|
||||
]);
|
||||
}
|
||||
67
src/Nonemm.Contests/Rules/SouthAmerica10.cs
Normal file
67
src/Nonemm.Contests/Rules/SouthAmerica10.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The SA 10 metre contest. A South American station is worth 4 points and
|
||||
/// everyone else 2. The exchange is a CQ zone, and both the prefixes and the
|
||||
/// zones are multipliers. One band, so nothing is counted per band.
|
||||
///
|
||||
/// N1MM logs this one through a `.udc` file, so the Cabrillo name here is the
|
||||
/// contest name and has not been checked against the sponsor's robot.
|
||||
public sealed class SouthAmerica10 : Contest
|
||||
{
|
||||
public string Name => "SA10";
|
||||
|
||||
public string DisplayName => "SA 10 Metre Contest";
|
||||
|
||||
public string CabrilloName => "SA10";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Zone", ExchangeSlot.Section, ExchangeFieldKind.CqZone),
|
||||
];
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Prefixes", "Zones"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerMode;
|
||||
|
||||
public bool HasSerialNumbers => false;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => me.CqZone.ToString();
|
||||
|
||||
public int PointsFor(QsoContext qso) => qso.Continent == "SA" ? 4 : 2;
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
List<Multiplier> found = [];
|
||||
if (qso.Qso.Call.WpxPrefix() is { } prefix)
|
||||
{
|
||||
found.Add(new Multiplier(1, prefix, ""));
|
||||
}
|
||||
string zone = qso.Qso.Section.Trim();
|
||||
if (zone.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, zone, ""));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(me.CqZone.ToString(), 3),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section, 3),
|
||||
]);
|
||||
}
|
||||
89
src/Nonemm.Contests/Rules/UkrainianDx.cs
Normal file
89
src/Nonemm.Contests/Rules/UkrainianDx.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The Ukrainian DX contest. A Ukrainian station sends its oblast, everyone
|
||||
/// else a serial number, and a contact with Ukraine is worth 10 points to a
|
||||
/// station outside it. The multipliers are the countries and the oblasts, each
|
||||
/// once per band.
|
||||
public sealed class UkrainianDx : Contest
|
||||
{
|
||||
public string Name => "UKRAINDX";
|
||||
|
||||
public string DisplayName => "Ukrainian DX";
|
||||
|
||||
public string CabrilloName => "UKRAINIAN-DX";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("Oblast", ExchangeSlot.Section, ExchangeFieldKind.Text) { Width = 3 },
|
||||
new ExchangeField("Nr", ExchangeSlot.Exchange1, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public bool SkipsField(ExchangeField field, CountryLookup? their) => field.Slot switch
|
||||
{
|
||||
ExchangeSlot.Section => their is not null && !IsUkrainian(their.Entity.PrimaryPrefix),
|
||||
ExchangeSlot.Exchange1 => their is not null && IsUkrainian(their.Entity.PrimaryPrefix),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Oblasts"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBandAndMode;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) =>
|
||||
IsUkrainian(me.CountryPrefix) ? me.County : "001";
|
||||
|
||||
public int PointsFor(QsoContext qso)
|
||||
{
|
||||
if (IsUkrainian(qso.CountryPrefix))
|
||||
{
|
||||
return IsUkrainian(qso.Me.CountryPrefix) ? 1 : 10;
|
||||
}
|
||||
if (qso.IsSameCountry)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return qso.IsSameContinent ? 2 : 3;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
List<Multiplier> found = [];
|
||||
if (qso.CountryPrefix.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(1, qso.CountryPrefix, band));
|
||||
}
|
||||
string oblast = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
if (IsUkrainian(qso.CountryPrefix) && oblast.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, oblast, band));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(SentExchangeFor(me), 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section.Length > 0 ? qso.Section : qso.Exchange1, 6),
|
||||
]);
|
||||
|
||||
private static bool IsUkrainian(string countryPrefix) => countryPrefix == "UR";
|
||||
}
|
||||
82
src/Nonemm.Contests/Rules/YoDxHf.cs
Normal file
82
src/Nonemm.Contests/Rules/YoDxHf.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
|
||||
namespace Nonemm.Contests.Rules;
|
||||
|
||||
/// The Romanian YO DX HF contest. A Romanian station sends its county and is
|
||||
/// worth 8 points; everyone else is 1 point inside your own country, 2 on your
|
||||
/// continent and 4 from another. The countries and the Romanian counties are
|
||||
/// multipliers, each once per band.
|
||||
public sealed class YoDxHf : Contest
|
||||
{
|
||||
public string Name => "YOHFDX";
|
||||
|
||||
public string DisplayName => "YO DX HF";
|
||||
|
||||
public string CabrilloName => "YO-DX-HF";
|
||||
|
||||
public IReadOnlyList<ExchangeField> ExchangeFieldsFor(StationInfo me) =>
|
||||
[
|
||||
new ExchangeField("RST", ExchangeSlot.ReceivedReport, ExchangeFieldKind.Report),
|
||||
new ExchangeField("County", ExchangeSlot.Section, ExchangeFieldKind.Text) { Width = 3 },
|
||||
new ExchangeField("Nr", ExchangeSlot.Exchange1, ExchangeFieldKind.Number),
|
||||
];
|
||||
|
||||
public bool SkipsField(ExchangeField field, CountryLookup? their) => field.Slot switch
|
||||
{
|
||||
ExchangeSlot.Section => their is not null && their.Entity.PrimaryPrefix != "YO",
|
||||
ExchangeSlot.Exchange1 => their is not null && their.Entity.PrimaryPrefix == "YO",
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> MultiplierNames => ["Countries", "Counties"];
|
||||
|
||||
public DupeScope DupeScope => DupeScope.PerBandAndMode;
|
||||
|
||||
public bool HasSerialNumbers => true;
|
||||
|
||||
public IReadOnlyList<ModeCategory> Modes => [ModeCategory.Cw, ModeCategory.Phone];
|
||||
|
||||
public string SentExchangeFor(StationInfo me) => me.CountryPrefix == "YO" ? me.County : "001";
|
||||
|
||||
public int PointsFor(QsoContext qso)
|
||||
{
|
||||
if (qso.CountryPrefix == "YO")
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
return qso.IsSameCountry ? 1 : qso.IsSameContinent ? 2 : 4;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Multiplier> MultipliersFor(QsoContext qso)
|
||||
{
|
||||
string band = qso.Band?.Name ?? "";
|
||||
List<Multiplier> found = [];
|
||||
if (qso.CountryPrefix.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(1, qso.CountryPrefix, band));
|
||||
}
|
||||
string county = qso.Qso.Section.Trim().ToUpperInvariant();
|
||||
if (qso.CountryPrefix == "YO" && county.Length > 0)
|
||||
{
|
||||
found.Add(new Multiplier(2, county, band));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public int TotalScore(ScoreTally tally, ContestEntry entry) =>
|
||||
tally.Points * tally.TotalMultipliers;
|
||||
|
||||
public CabrilloExchange CabrilloExchange(Qso qso, StationInfo me, ContestEntry entry) =>
|
||||
new(
|
||||
[
|
||||
new CabrilloField(me.Callsign, 13),
|
||||
new CabrilloField(qso.SentReport, 3),
|
||||
new CabrilloField(SentExchangeFor(me), 6),
|
||||
],
|
||||
[
|
||||
new CabrilloField(qso.Call.Text, 13),
|
||||
new CabrilloField(qso.ReceivedReport, 3),
|
||||
new CabrilloField(qso.Section.Length > 0 ? qso.Section : qso.Exchange1, 6),
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user